feat(ui): Enhance resource creation experience when limits are reached (#19103)

* feat(ui): add AssetLimitOverlay

* feat(ui): enable create bucket button

* feat(ui): enable create dashboard and task buttons

* feat(ui): add reusable AssetLimitButton

* feat(ui): change alerts limit experience

* feat(ui): update changelog

* feat(ui): address review comments
pull/19153/head
Daniel Campbell 2020-07-30 09:14:21 -07:00 committed by GitHub
parent 48814abab1
commit bc4c19dfd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 375 additions and 125 deletions

View File

@ -8,15 +8,16 @@
### Features ### Features
1. [19075](https://github.com/influxdata/influxdb/pull/19075): Add resource links to a stack's resources from public HTTP API list/read calls 1. [19075](https://github.com/influxdata/influxdb/pull/19075): Add resource links to a stack's resources from public HTTP API list/read calls
1. [19103](https://github.com/influxdata/influxdb/pull/19103): Enhance resource creation experience when limits are reached
### Bug Fixes ### Bug Fixes
1. [19043](https://github.com/influxdata/influxdb/pull/19043): Enforce all influx CLI flag args are valid 1. [19043](https://github.com/influxdata/influxdb/pull/19043): Enforce all influx CLI flag args are valid
## v2.0.0-beta.15 [2020-07-23] ## v2.0.0-beta.15 [2020-07-23]
### Breaking ### Breaking
1. [19004](https://github.com/influxdata/influxdb/pull/19004): Removed the `migrate` command from the `influxd` binary. 1. [19004](https://github.com/influxdata/influxdb/pull/19004): Removed the `migrate` command from the `influxd` binary.
1. [18921](https://github.com/influxdata/influxdb/pull/18921): Restricted UI variable names to not clash with Flux reserved words 1. [18921](https://github.com/influxdata/influxdb/pull/18921): Restricted UI variable names to not clash with Flux reserved words
@ -41,7 +42,6 @@
1. [18989](https://github.com/influxdata/influxdb/pull/18989): Stopped fetching tags in the advanced builder 1. [18989](https://github.com/influxdata/influxdb/pull/18989): Stopped fetching tags in the advanced builder
1. [19044](https://github.com/influxdata/influxdb/pull/19044): Graph customization: X and Y axis properly accept values 1. [19044](https://github.com/influxdata/influxdb/pull/19044): Graph customization: X and Y axis properly accept values
## v2.0.0-beta.14 [2020-07-08] ## v2.0.0-beta.14 [2020-07-08]
### Features ### Features

View File

@ -3,8 +3,8 @@ import React, {FC, ReactChild, useState} from 'react'
import {connect} from 'react-redux' import {connect} from 'react-redux'
// Types // Types
import {AppState, ResourceType} from 'src/types' import {AppState, ResourceType, ColumnTypes} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits' import {LimitStatus, MonitoringLimits} from 'src/cloud/actions/limits'
// Components // Components
import { import {
@ -20,14 +20,17 @@ import {
ComponentColor, ComponentColor,
} from '@influxdata/clockface' } from '@influxdata/clockface'
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert' import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
import AssetLimitButton from 'src/cloud/components/AssetLimitButton'
// Utils // Utils
import {extractMonitoringLimitStatus} from 'src/cloud/utils/limits' import {
extractChecksLimits,
extractRulesLimits,
extractEndpointsLimits,
} from 'src/cloud/utils/limits'
type ColumnTypes = // Constants
| ResourceType.NotificationRules import {CLOUD} from 'src/shared/constants'
| ResourceType.NotificationEndpoints
| ResourceType.Checks
interface OwnProps { interface OwnProps {
type: ColumnTypes type: ColumnTypes
@ -38,7 +41,7 @@ interface OwnProps {
} }
interface StateProps { interface StateProps {
limitStatus: LimitStatus limitStatus: MonitoringLimits
} }
const AlertsColumnHeader: FC<OwnProps & StateProps> = ({ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
@ -53,6 +56,20 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
const formattedTitle = title.toLowerCase().replace(' ', '-') const formattedTitle = title.toLowerCase().replace(' ', '-')
const panelClassName = `alerting-index--column alerting-index--${formattedTitle}` const panelClassName = `alerting-index--column alerting-index--${formattedTitle}`
const resourceName = title.substr(0, title.length - 1)
const isLimitExceeded =
CLOUD &&
limitStatus[type] === LimitStatus.EXCEEDED &&
type !== ResourceType.Checks
const assetLimitButton = (
<AssetLimitButton
color={ComponentColor.Secondary}
buttonText="Create"
resourceName={resourceName}
/>
)
return ( return (
<Panel <Panel
@ -70,7 +87,7 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
tooltipContents={questionMarkTooltipContents} tooltipContents={questionMarkTooltipContents}
/> />
</FlexBox> </FlexBox>
{createButton} {isLimitExceeded ? assetLimitButton : createButton}
</Panel.Header> </Panel.Header>
<div className="alerting-index--search"> <div className="alerting-index--search">
<Input <Input
@ -88,7 +105,10 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
> >
<div className="alerting-index--list"> <div className="alerting-index--list">
{children(searchTerm)} {children(searchTerm)}
<AssetLimitAlert resourceName={title} limitStatus={limitStatus} /> <AssetLimitAlert
resourceName={title}
limitStatus={limitStatus[type]}
/>
</div> </div>
</DapperScrollbars> </DapperScrollbars>
</div> </div>
@ -98,7 +118,11 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
const mstp = ({cloud: {limits}}: AppState) => { const mstp = ({cloud: {limits}}: AppState) => {
return { return {
limitStatus: extractMonitoringLimitStatus(limits), limitStatus: {
[ResourceType.Checks]: extractChecksLimits(limits),
[ResourceType.NotificationRules]: extractRulesLimits(limits),
[ResourceType.NotificationEndpoints]: extractEndpointsLimits(limits),
},
} }
} }

View File

@ -3,12 +3,8 @@ import React, {FC, useEffect} from 'react'
import {connect, ConnectedProps, useDispatch} from 'react-redux' import {connect, ConnectedProps, useDispatch} from 'react-redux'
// Components // Components
import { import {Button, IconFont, ComponentColor} from '@influxdata/clockface'
Button, import AssetLimitButton from 'src/cloud/components/AssetLimitButton'
IconFont,
ComponentColor,
ComponentStatus,
} from '@influxdata/clockface'
// Actions // Actions
import {checkBucketLimits, LimitStatus} from 'src/cloud/actions/limits' import {checkBucketLimits, LimitStatus} from 'src/cloud/actions/limits'
@ -20,10 +16,12 @@ import {extractBucketLimits} from 'src/cloud/utils/limits'
// Types // Types
import {AppState} from 'src/types' import {AppState} from 'src/types'
type ReduxProps = ConnectedProps<typeof connector> // Constants
type Props = ReduxProps import {CLOUD} from 'src/shared/constants'
const CreateBucketButton: FC<Props> = ({ type ReduxProps = ConnectedProps<typeof connector>
const CreateBucketButton: FC<ReduxProps> = ({
limitStatus, limitStatus,
onShowOverlay, onShowOverlay,
onDismissOverlay, onDismissOverlay,
@ -34,33 +32,22 @@ const CreateBucketButton: FC<Props> = ({
dispatch(checkBucketLimits()) dispatch(checkBucketLimits())
}, [dispatch]) }, [dispatch])
const limitExceeded = limitStatus === LimitStatus.EXCEEDED const handleItemClick = (): void => {
const text = 'Create Bucket' onShowOverlay('create-bucket', null, onDismissOverlay)
let titleText = 'Click to create a bucket'
let buttonStatus = ComponentStatus.Default
if (limitExceeded) {
titleText = 'This account has the maximum number of buckets allowed'
buttonStatus = ComponentStatus.Disabled
} }
const handleItemClick = (): void => { if (CLOUD && limitStatus === LimitStatus.EXCEEDED) {
if (limitExceeded) { return <AssetLimitButton resourceName="Bucket" />
return
}
onShowOverlay('create-bucket', null, onDismissOverlay)
} }
return ( return (
<Button <Button
icon={IconFont.Plus} icon={IconFont.Plus}
color={ComponentColor.Primary} color={ComponentColor.Primary}
text={text} text="Create Bucket"
titleText={titleText} titleText="Click to create a bucket"
onClick={handleItemClick} onClick={handleItemClick}
testID="Create Bucket" testID="Create Bucket"
status={buttonStatus}
/> />
) )
} }

View File

@ -23,6 +23,9 @@ import {
ResourceType, ResourceType,
} from 'src/types' } from 'src/types'
// Utils
import {extractChecksLimits} from 'src/cloud/utils/limits'
type ReduxProps = ConnectedProps<typeof connector> type ReduxProps = ConnectedProps<typeof connector>
type Props = ReduxProps & RouteComponentProps<{orgID: string}> type Props = ReduxProps & RouteComponentProps<{orgID: string}>
@ -34,6 +37,7 @@ const ChecksColumn: FunctionComponent<Props> = ({
}, },
rules, rules,
endpoints, endpoints,
limitStatus,
}) => { }) => {
const handleCreateThreshold = () => { const handleCreateThreshold = () => {
history.push(`/orgs/${orgID}/alerting/checks/new-threshold`) history.push(`/orgs/${orgID}/alerting/checks/new-threshold`)
@ -66,6 +70,7 @@ const ChecksColumn: FunctionComponent<Props> = ({
const createButton = ( const createButton = (
<CreateCheckDropdown <CreateCheckDropdown
limitStatus={limitStatus}
onCreateThreshold={handleCreateThreshold} onCreateThreshold={handleCreateThreshold}
onCreateDeadman={handleCreateDeadman} onCreateDeadman={handleCreateDeadman}
/> />
@ -92,6 +97,10 @@ const ChecksColumn: FunctionComponent<Props> = ({
} }
const mstp = (state: AppState) => { const mstp = (state: AppState) => {
const {
cloud: {limits},
} = state
const checks = getAll<Check>(state, ResourceType.Checks) const checks = getAll<Check>(state, ResourceType.Checks)
const endpoints = getAll<NotificationEndpoint>( const endpoints = getAll<NotificationEndpoint>(
@ -108,6 +117,7 @@ const mstp = (state: AppState) => {
checks: sortChecksByName(checks), checks: sortChecksByName(checks),
rules: sortRulesByName(rules), rules: sortRulesByName(rules),
endpoints: sortEndpointsByName(endpoints), endpoints: sortEndpointsByName(endpoints),
limitStatus: extractChecksLimits(limits),
} }
} }

View File

@ -1,22 +1,41 @@
// Libraries // Libraries
import React, {FunctionComponent, MouseEvent} from 'react' import React, {FunctionComponent, MouseEvent} from 'react'
import {connect, ConnectedProps} from 'react-redux'
// Components // Components
import {Dropdown, ComponentColor, IconFont} from '@influxdata/clockface' import {Dropdown, ComponentColor, IconFont} from '@influxdata/clockface'
// Actions
import {showOverlay, dismissOverlay} from 'src/overlays/actions/overlays'
// Types // Types
import {CheckType} from 'src/types' import {CheckType} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits'
interface Props { // Constants
import {CLOUD} from 'src/shared/constants'
interface OwnProps {
onCreateThreshold: () => void onCreateThreshold: () => void
onCreateDeadman: () => void onCreateDeadman: () => void
limitStatus: LimitStatus
} }
const CreateCheckDropdown: FunctionComponent<Props> = ({ type ReduxProps = ConnectedProps<typeof connector>
const CreateCheckDropdown: FunctionComponent<OwnProps & ReduxProps> = ({
onCreateThreshold, onCreateThreshold,
onCreateDeadman, onCreateDeadman,
onDismissOverlay,
onShowOverlay,
limitStatus,
}) => { }) => {
const handleItemClick = (type: CheckType): void => { const handleItemClick = (type: CheckType): void => {
if (CLOUD && limitStatus === LimitStatus.EXCEEDED) {
onShowOverlay('asset-limit', {asset: 'Checks'}, onDismissOverlay)
return
}
if (type === 'threshold') { if (type === 'threshold') {
onCreateThreshold() onCreateThreshold()
} }
@ -69,4 +88,11 @@ const CreateCheckDropdown: FunctionComponent<Props> = ({
) )
} }
export default CreateCheckDropdown const mdtp = {
onShowOverlay: showOverlay,
onDismissOverlay: dismissOverlay,
}
const connector = connect(null, mdtp)
export default connector(CreateCheckDropdown)

View File

@ -17,6 +17,7 @@ import {
Limits, Limits,
RemoteDataState, RemoteDataState,
ResourceType, ResourceType,
ColumnTypes,
} from 'src/types' } from 'src/types'
// Selectors // Selectors
@ -36,6 +37,10 @@ export enum LimitStatus {
EXCEEDED = 'exceeded', EXCEEDED = 'exceeded',
} }
export type MonitoringLimits = {
[type in ColumnTypes]: LimitStatus
}
export enum ActionTypes { export enum ActionTypes {
SetLimits = 'SET_LIMITS', SetLimits = 'SET_LIMITS',
SetLimitsStatus = 'SET_LIMITS_STATUS', SetLimitsStatus = 'SET_LIMITS_STATUS',

View File

@ -0,0 +1,48 @@
// Libraries
import React, {FC} from 'react'
import {connect, ConnectedProps} from 'react-redux'
// Components
import {Button, ComponentColor, IconFont} from '@influxdata/clockface'
// Actions
import {showOverlay, dismissOverlay} from 'src/overlays/actions/overlays'
interface OwnProps {
color?: ComponentColor
resourceName: string
buttonText?: string
}
type ReduxProps = ConnectedProps<typeof connector>
const AssetLimitButton: FC<OwnProps & ReduxProps> = ({
color = ComponentColor.Primary,
buttonText,
resourceName,
onShowOverlay,
onDismissOverlay,
}) => {
const handleClick = () => {
onShowOverlay('asset-limit', {asset: `${resourceName}s`}, onDismissOverlay)
}
return (
<Button
icon={IconFont.Plus}
text={buttonText || `Create ${resourceName}`}
color={color}
titleText={`Click to create ${resourceName}`}
onClick={handleClick}
testID={`create-${resourceName.toLowerCase()}`}
/>
)
}
const mdtp = {
onShowOverlay: showOverlay,
onDismissOverlay: dismissOverlay,
}
const connector = connect(null, mdtp)
export default connector(AssetLimitButton)

View File

@ -0,0 +1,71 @@
// Libraries
import React, {FC} from 'react'
import {connect} from 'react-redux'
import {get} from 'lodash'
// Components
import {
OverlayContainer,
Overlay,
ComponentSize,
GradientBox,
Gradients,
InfluxColors,
} from '@influxdata/clockface'
import CloudUpgradeButton from 'src/shared/components/CloudUpgradeButton'
// Types
import {AppState} from 'src/types'
interface OwnProps {
onClose: () => void
}
interface StateProps {
assetName: string
}
const AssetLimitOverlay: FC<OwnProps & StateProps> = ({assetName, onClose}) => {
return (
<OverlayContainer
maxWidth={600}
testID="asset-limit-overlay"
className="asset-limit-overlay"
>
<GradientBox
borderGradient={Gradients.MiyazakiSky}
borderColor={InfluxColors.Raven}
>
<div className="asset-limit-overlay--contents">
<Overlay.Header
title={`Need More ${assetName}?`}
wrapText={true}
onDismiss={onClose}
/>
<Overlay.Body>
<div className="asset-limit-overlay--graphic-container">
<div className="asset-limit-overlay--graphic">
<div className="asset-limit-overlay--graphic-element" />
</div>
</div>
<p>
Youve reached the maximum allotted {assetName} on your current
plan. Upgrade to Pay as you go to create more {assetName}.
</p>
</Overlay.Body>
<Overlay.Footer>
<CloudUpgradeButton size={ComponentSize.Large} />
</Overlay.Footer>
</div>
</GradientBox>
</OverlayContainer>
)
}
const mstp = (state: AppState) => {
return {
assetName: get(state, 'overlays.params.asset', ''),
}
}
export default connect(mstp)(AssetLimitOverlay)

View File

@ -34,10 +34,16 @@ export const extractTaskMax = (limits: LimitsState): number => {
return get(limits, 'tasks.maxAllowed') || Infinity return get(limits, 'tasks.maxAllowed') || Infinity
} }
export const extractChecksLimits = (limits: LimitsState): LimitStatus => {
return get(limits, 'checks.limitStatus')
}
export const extractChecksMax = (limits: LimitsState): number => { export const extractChecksMax = (limits: LimitsState): number => {
return get(limits, 'checks.maxAllowed') || Infinity return get(limits, 'checks.maxAllowed') || Infinity
} }
export const extractRulesLimits = (limits: LimitsState): LimitStatus => {
return get(limits, 'rules.limitStatus')
}
export const extractRulesMax = (limits: LimitsState): number => { export const extractRulesMax = (limits: LimitsState): number => {
return get(limits, 'rules.maxAllowed') || Infinity return get(limits, 'rules.maxAllowed') || Infinity
} }
@ -45,6 +51,9 @@ export const extractBlockedRules = (limits: LimitsState): string[] => {
return get(limits, 'rules.blocked') || [] return get(limits, 'rules.blocked') || []
} }
export const extractEndpointsLimits = (limits: LimitsState): LimitStatus => {
return get(limits, 'endpoints.limitStatus')
}
export const extractEndpointsMax = (limits: LimitsState): number => { export const extractEndpointsMax = (limits: LimitsState): number => {
return get(limits, 'endpoints.maxAllowed') || Infinity return get(limits, 'endpoints.maxAllowed') || Infinity
} }
@ -52,22 +61,6 @@ export const extractBlockedEndpoints = (limits: LimitsState): string[] => {
return get(limits, 'endpoints.blocked') || [] return get(limits, 'endpoints.blocked') || []
} }
export const extractMonitoringLimitStatus = (
limits: LimitsState
): LimitStatus => {
const statuses = [
get(limits, 'rules.limitStatus'),
get(limits, 'endpoints.limitStatus'),
get(limits, 'checks.limitStatus'),
]
if (statuses.includes(LimitStatus.EXCEEDED)) {
return LimitStatus.EXCEEDED
}
return LimitStatus.OK
}
export const extractLimitedMonitoringResources = ( export const extractLimitedMonitoringResources = (
limits: LimitsState limits: LimitsState
): string => { ): string => {

View File

@ -29,8 +29,7 @@ import {setDashboardSort} from 'src/dashboards/actions/creators'
// Types // Types
import {AppState, ResourceType} from 'src/types' import {AppState, ResourceType} from 'src/types'
import {LimitStatus} from 'src/cloud/actions/limits' import {Sort} from '@influxdata/clockface'
import {ComponentStatus, Sort} from '@influxdata/clockface'
import {SortTypes} from 'src/shared/utils/sort' import {SortTypes} from 'src/shared/utils/sort'
import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems' import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
@ -52,7 +51,7 @@ class DashboardIndex extends PureComponent<Props, State> {
} }
public render() { public render() {
const {createDashboard, sortOptions} = this.props const {createDashboard, sortOptions, limitStatus} = this.props
const {searchTerm} = this.state const {searchTerm} = this.state
return ( return (
@ -87,7 +86,7 @@ class DashboardIndex extends PureComponent<Props, State> {
onSelectTemplate={this.summonImportFromTemplateOverlay} onSelectTemplate={this.summonImportFromTemplateOverlay}
resourceName="Dashboard" resourceName="Dashboard"
canImportFromTemplate={true} canImportFromTemplate={true}
status={this.addResourceStatus} limitStatus={limitStatus}
/> />
</Page.ControlBarRight> </Page.ControlBarRight>
</Page.ControlBar> </Page.ControlBar>
@ -156,14 +155,6 @@ class DashboardIndex extends PureComponent<Props, State> {
} = this.props } = this.props
history.push(`/orgs/${orgID}/dashboards-list/import/template`) history.push(`/orgs/${orgID}/dashboards-list/import/template`)
} }
private get addResourceStatus(): ComponentStatus {
const {limitStatus} = this.props
if (limitStatus === LimitStatus.EXCEEDED) {
return ComponentStatus.Disabled
}
return ComponentStatus.Default
}
} }
const mstp = (state: AppState) => { const mstp = (state: AppState) => {

View File

@ -14,6 +14,7 @@ import TelegrafConfigOverlay from 'src/telegrafs/components/TelegrafConfigOverla
import TelegrafOutputOverlay from 'src/telegrafs/components/TelegrafOutputOverlay' import TelegrafOutputOverlay from 'src/telegrafs/components/TelegrafOutputOverlay'
import OrgSwitcherOverlay from 'src/pageLayout/components/OrgSwitcherOverlay' import OrgSwitcherOverlay from 'src/pageLayout/components/OrgSwitcherOverlay'
import CreateBucketOverlay from 'src/buckets/components/CreateBucketOverlay' import CreateBucketOverlay from 'src/buckets/components/CreateBucketOverlay'
import AssetLimitOverlay from 'src/cloud/components/AssetLimitOverlay'
// Actions // Actions
import {dismissOverlay} from 'src/overlays/actions/overlays' import {dismissOverlay} from 'src/overlays/actions/overlays'
@ -57,6 +58,9 @@ const OverlayController: FunctionComponent<OverlayControllerProps> = props => {
case 'create-bucket': case 'create-bucket':
activeOverlay = <CreateBucketOverlay onClose={closer} /> activeOverlay = <CreateBucketOverlay onClose={closer} />
break break
case 'asset-limit':
activeOverlay = <AssetLimitOverlay onClose={closer} />
break
default: default:
visibility = false visibility = false
} }

View File

@ -13,6 +13,7 @@ export type OverlayID =
| 'telegraf-output' | 'telegraf-output'
| 'switch-organizations' | 'switch-organizations'
| 'create-bucket' | 'create-bucket'
| 'asset-limit'
export interface OverlayParams { export interface OverlayParams {
[key: string]: string [key: string]: string

View File

@ -1,5 +1,6 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {PureComponent} from 'react'
import {connect, ConnectedProps} from 'react-redux'
import _ from 'lodash' import _ from 'lodash'
// Components // Components
@ -11,11 +12,21 @@ import {
ComponentStatus, ComponentStatus,
} from '@influxdata/clockface' } from '@influxdata/clockface'
// Actions
import {showOverlay, dismissOverlay} from 'src/overlays/actions/overlays'
// Types
import {LimitStatus} from 'src/cloud/actions/limits'
// Constants
import {CLOUD} from 'src/shared/constants'
interface OwnProps { interface OwnProps {
onSelectNew: () => void onSelectNew: () => void
onSelectImport: () => void onSelectImport: () => void
onSelectTemplate?: () => void onSelectTemplate?: () => void
resourceName: string resourceName: string
limitStatus?: LimitStatus
} }
interface DefaultProps { interface DefaultProps {
@ -24,9 +35,11 @@ interface DefaultProps {
titleText: string titleText: string
} }
type Props = OwnProps & DefaultProps type ReduxProps = ConnectedProps<typeof connector>
export default class AddResourceDropdown extends PureComponent<Props> { type Props = OwnProps & DefaultProps & ReduxProps
class AddResourceDropdown extends PureComponent<Props> {
public static defaultProps: DefaultProps = { public static defaultProps: DefaultProps = {
canImportFromTemplate: false, canImportFromTemplate: false,
status: ComponentStatus.Default, status: ComponentStatus.Default,
@ -121,8 +134,23 @@ export default class AddResourceDropdown extends PureComponent<Props> {
return `From a Template` return `From a Template`
} }
private handleLimit = (): void => {
const {resourceName, onShowOverlay, onDismissOverlay} = this.props
onShowOverlay('asset-limit', {asset: `${resourceName}s`}, onDismissOverlay)
}
private handleSelect = (selection: string): void => { private handleSelect = (selection: string): void => {
const {onSelectNew, onSelectImport, onSelectTemplate} = this.props const {
onSelectNew,
onSelectImport,
onSelectTemplate,
limitStatus = LimitStatus.OK,
} = this.props
if (CLOUD && limitStatus === LimitStatus.EXCEEDED) {
this.handleLimit()
return
}
if (selection === this.newOption) { if (selection === this.newOption) {
onSelectNew() onSelectNew()
@ -135,3 +163,12 @@ export default class AddResourceDropdown extends PureComponent<Props> {
} }
} }
} }
const mdtp = {
onShowOverlay: showOverlay,
onDismissOverlay: dismissOverlay,
}
const connector = connect(null, mdtp)
export default connector(AddResourceDropdown)

View File

@ -30,10 +30,12 @@ interface StateProps {
interface OwnProps { interface OwnProps {
className?: string className?: string
buttonText?: string buttonText?: string
size?: ComponentSize
} }
const CloudUpgradeButton: FC<StateProps & OwnProps> = ({ const CloudUpgradeButton: FC<StateProps & OwnProps> = ({
inView, inView,
size = ComponentSize.Small,
className, className,
buttonText = 'Upgrade Now', buttonText = 'Upgrade Now',
}) => { }) => {
@ -47,7 +49,7 @@ const CloudUpgradeButton: FC<StateProps & OwnProps> = ({
<LinkButton <LinkButton
className={cloudUpgradeButtonClass} className={cloudUpgradeButtonClass}
color={ComponentColor.Success} color={ComponentColor.Success}
size={ComponentSize.Small} size={size}
shape={ButtonShape.Default} shape={ButtonShape.Default}
href={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`} href={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self" target="_self"

View File

@ -1,67 +1,119 @@
.load-data--asset-alert { .load-data--asset-alert {
margin-bottom: $cf-marg-d; margin-bottom: $cf-marg-d;
} }
.cf-page-control-bar { .cf-page-control-bar {
+ .rate-alert { + .rate-alert {
margin: 0 $cf-marg-c $cf-marg-c; margin: 0 $cf-marg-c $cf-marg-c;
} }
} }
.rate-alert--content { .rate-alert--content {
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
font-weight: 500; font-weight: 500;
.rate-alert--button { .rate-alert--button {
margin-top: $cf-marg-b; margin-top: $cf-marg-b;
} }
} }
.cf-page--title { .cf-page--title {
flex-shrink: 0; flex-shrink: 0;
} }
.asset-alert { .asset-alert {
height: 100%; height: 100%;
background-position: bottom -20px left -20px; background-position: bottom -20px left -20px;
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
background-image: url('../../assets/images/dashboard-empty--dark.svg'); background-image: url('~assets/images/dashboard-empty--dark.svg');
} }
.dashboards--asset-alert { .dashboards--asset-alert {
.asset-alert--contents { .asset-alert--contents {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
} }
} }
a.upgrade-payg--button { a.upgrade-payg--button {
@include gradient-diag-up($c-pool, $c-rainforest); @include gradient-diag-up($c-pool, $c-rainforest);
}
.asset-limit-overlay--contents {
background-color: $g1-raven;
border-radius: $cf-radius;
p {
text-align: center;
}
.cf-overlay--title {
word-break: break-word;
}
}
.asset-limit-overlay--graphic-container {
width: 100%;
display: inline-flex;
justify-content: center;
}
.asset-limit-overlay--graphic {
display: inline-flex;
position: relative;
width: 100%;
max-width: 720px;
}
.asset-limit-overlay--graphic:after {
content: '';
display: inline-block;
padding-bottom: 57.3170731707317%;
}
.asset-limit-overlay--graphic-element {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-position: center center;
background-size: contain;
background-repeat: no-repeat;
background-image: url('~assets/images/dashboard-empty--dark.svg');
} }
@media screen and (min-width: $cf-nav-menu--breakpoint) { @media screen and (min-width: $cf-nav-menu--breakpoint) {
.rate-alert { .rate-alert {
margin-left: $cf-marg-d; margin-left: $cf-marg-d;
} }
.rate-alert--content { .rate-alert--content {
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
height: 100%; height: 100%;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
.rate-alert--button { .rate-alert--button {
margin-top: 0; margin-top: 0;
margin-left: $cf-marg-c; margin-left: $cf-marg-c;
}
} }
}
.cf-page-control-bar { .cf-page-control-bar {
+ .rate-alert { + .rate-alert {
margin: 0 $cf-marg-d $cf-marg-c; margin: 0 $cf-marg-d $cf-marg-c;
}
} }
}
.asset-limit-overlay--graphic {
width: 60%;
}
.asset-limit-overlay--contents {
.cf-overlay--header__wrap {
align-items: center;
}
}
} }

View File

@ -6,7 +6,6 @@ import {
InputLabel, InputLabel,
SlideToggle, SlideToggle,
ComponentSize, ComponentSize,
ComponentStatus,
Page, Page,
Sort, Sort,
FlexBox, FlexBox,
@ -57,6 +56,7 @@ export default class TasksHeader extends PureComponent<Props> {
sortType, sortType,
sortDirection, sortDirection,
onSort, onSort,
limitStatus,
} = this.props } = this.props
return ( return (
@ -98,19 +98,11 @@ export default class TasksHeader extends PureComponent<Props> {
onSelectImport={onImportTask} onSelectImport={onImportTask}
onSelectTemplate={onImportFromTemplate} onSelectTemplate={onImportFromTemplate}
resourceName="Task" resourceName="Task"
status={this.addResourceStatus} limitStatus={limitStatus}
/> />
</Page.ControlBarRight> </Page.ControlBarRight>
</Page.ControlBar> </Page.ControlBar>
</> </>
) )
} }
private get addResourceStatus(): ComponentStatus {
const {limitStatus} = this.props
if (limitStatus === LimitStatus.EXCEEDED) {
return ComponentStatus.Disabled
}
return ComponentStatus.Default
}
} }

View File

@ -20,7 +20,9 @@ import {
CheckBase as GenCheckBase, CheckBase as GenCheckBase,
NotificationEndpointBase as GenEndpointBase, NotificationEndpointBase as GenEndpointBase,
} from 'src/client' } from 'src/client'
import {RemoteDataState} from 'src/types' import {RemoteDataState} from 'src/types'
import {ResourceType} from './resources'
type Omit<T, U> = Pick<T, Exclude<keyof T, U>> type Omit<T, U> = Pick<T, Exclude<keyof T, U>>
type Overwrite<T, U> = Omit<T, keyof U> & U type Overwrite<T, U> = Omit<T, keyof U> & U
@ -30,6 +32,11 @@ interface WithClientID<T> {
value: T value: T
} }
export type ColumnTypes =
| ResourceType.NotificationRules
| ResourceType.NotificationEndpoints
| ResourceType.Checks
/* Endpoints */ /* Endpoints */
type EndpointOverrides = { type EndpointOverrides = {
status: RemoteDataState status: RemoteDataState