diff --git a/ui/src/buckets/actions/index.ts b/ui/src/buckets/actions/index.ts index 46d75cfa15..c424471ccb 100644 --- a/ui/src/buckets/actions/index.ts +++ b/ui/src/buckets/actions/index.ts @@ -6,10 +6,18 @@ import {client} from 'src/utils/api' // Types import {RemoteDataState, AppState, Bucket} from 'src/types' +// Utils +import {isLimitError, extractMessage} from 'src/cloud/utils/limits' + // Actions import {notify} from 'src/shared/actions/notifications' -import {getBucketsFailed} from 'src/shared/copy/notifications' +import {checkBucketLimits} from 'src/cloud/actions/limits' +// Constants +import { + getBucketsFailed, + resourceLimitReached, +} from 'src/shared/copy/notifications' import { bucketCreateFailed, bucketUpdateFailed, @@ -106,10 +114,15 @@ export const createBucket = (bucket: Bucket) => async ( }) dispatch(addBucket(createdBucket)) - } catch (e) { - console.error(e) - dispatch(notify(bucketCreateFailed())) - throw e + dispatch(checkBucketLimits()) + } catch (error) { + console.error(error) + if (isLimitError(error)) { + const message = extractMessage(error) + dispatch(notify(resourceLimitReached('buckets', message))) + } else { + dispatch(notify(bucketCreateFailed())) + } } } @@ -149,6 +162,7 @@ export const deleteBucket = (id: string, name: string) => async ( await client.buckets.delete(id) dispatch(removeBucket(id)) + dispatch(checkBucketLimits()) } catch (e) { console.error(e) dispatch(notify(bucketDeleteFailed(name))) diff --git a/ui/src/buckets/components/BucketsTab.tsx b/ui/src/buckets/components/BucketsTab.tsx index fac32c23dc..6e1069d337 100644 --- a/ui/src/buckets/components/BucketsTab.tsx +++ b/ui/src/buckets/components/BucketsTab.tsx @@ -6,7 +6,7 @@ import {connect} from 'react-redux' // Components import {ErrorHandling} from 'src/shared/decorators/errors' import {Input, Button, EmptyState} from '@influxdata/clockface' -import {Overlay, Tabs} from 'src/clockface' +import {Overlay, Tabs, ComponentStatus} from 'src/clockface' import FilterList from 'src/shared/components/Filter' import BucketList from 'src/buckets/components/BucketList' import {PrettyBucket} from 'src/buckets/components/BucketRow' @@ -14,6 +14,10 @@ import CreateBucketOverlay from 'src/buckets/components/CreateBucketOverlay' // Actions import {createBucket, updateBucket, deleteBucket} from 'src/buckets/actions' +import { + checkBucketLimits as checkBucketLimitsAction, + LimitStatus, +} from 'src/cloud/actions/limits' // Utils import {prettyBuckets} from 'src/shared/utils/prettyBucket' @@ -32,12 +36,14 @@ import {SortTypes} from 'src/shared/utils/sort' interface StateProps { org: Organization buckets: Bucket[] + limitStatus: LimitStatus } interface DispatchProps { createBucket: typeof createBucket updateBucket: typeof updateBucket deleteBucket: typeof deleteBucket + checkBucketLimits: typeof checkBucketLimitsAction } interface State { @@ -66,6 +72,10 @@ class BucketsTab extends PureComponent { } } + public componentDidMount() { + this.props.checkBucketLimits() + } + public render() { const {org, buckets} = this.props const { @@ -93,6 +103,8 @@ class BucketsTab extends PureComponent { color={ComponentColor.Primary} onClick={this.handleOpenModal} testID="Create Bucket" + status={this.createButtonStatus} + titleText={this.createButtonTitleText} /> @@ -163,6 +175,20 @@ class BucketsTab extends PureComponent { this.setState({searchTerm}) } + private get createButtonStatus(): ComponentStatus { + if (this.props.limitStatus === LimitStatus.EXCEEDED) { + return ComponentStatus.Disabled + } + return ComponentStatus.Default + } + + private get createButtonTitleText(): string { + if (this.props.limitStatus === LimitStatus.EXCEEDED) { + return 'This account has the maximum number of buckets allowed' + } + return 'Create a bucket' + } + private get emptyState(): JSX.Element { const {searchTerm} = this.state @@ -191,17 +217,25 @@ class BucketsTab extends PureComponent { } } -const mstp = ({buckets, orgs}: AppState): StateProps => { - return { - buckets: buckets.list, - org: orgs.org, - } -} +const mstp = ({ + buckets, + orgs, + cloud: { + limits: { + buckets: {limitStatus}, + }, + }, +}: AppState): StateProps => ({ + buckets: buckets.list, + org: orgs.org, + limitStatus, +}) const mdtp = { createBucket, updateBucket, deleteBucket, + checkBucketLimits: checkBucketLimitsAction, } export default connect( diff --git a/ui/src/buckets/containers/BucketsIndex.tsx b/ui/src/buckets/containers/BucketsIndex.tsx index dbab621340..1c1ec850a5 100644 --- a/ui/src/buckets/containers/BucketsIndex.tsx +++ b/ui/src/buckets/containers/BucketsIndex.tsx @@ -15,6 +15,7 @@ import GetResources, {ResourceTypes} from 'src/shared/components/GetResources' // Types import {Organization} from '@influxdata/influx' import {AppState} from 'src/types' +import GetAssetLimits from 'src/cloud/components/GetAssetLimits' interface StateProps { org: Organization @@ -41,8 +42,10 @@ class BucketsIndex extends Component { > - - {this.props.children} + + + {this.props.children} + diff --git a/ui/src/cloud/actions/limits.ts b/ui/src/cloud/actions/limits.ts index 0a9669ffee..4dcb1b6b1e 100644 --- a/ui/src/cloud/actions/limits.ts +++ b/ui/src/cloud/actions/limits.ts @@ -46,9 +46,14 @@ export enum ActionTypes { SetLimits = 'SET_LIMITS', SetLimitsStatus = 'SET_LIMITS_STATUS', SetDashboardLimitStatus = 'SET_DASHBOARD_LIMIT_STATUS', + SetBucketLimitStatus = 'SET_BUCKET_LIMIT_STATUS', } -export type Actions = SetLimits | SetLimitsStatus | SetDashboardLimitStatus +export type Actions = + | SetLimits + | SetLimitsStatus + | SetDashboardLimitStatus + | SetBucketLimitStatus export interface SetLimits { type: ActionTypes.SetLimits @@ -76,6 +81,20 @@ export const setDashboardLimitStatus = ( } } +export interface SetBucketLimitStatus { + type: ActionTypes.SetBucketLimitStatus + payload: {limitStatus: LimitStatus} +} + +export const setBucketLimitStatus = ( + limitStatus: LimitStatus +): SetBucketLimitStatus => { + return { + type: ActionTypes.SetBucketLimitStatus, + payload: {limitStatus}, + } +} + export interface SetLimitsStatus { type: ActionTypes.SetLimitsStatus payload: { @@ -154,3 +173,30 @@ export const checkDashboardLimits = () => ( console.error(e) } } + +export const checkBucketLimits = () => async ( + dispatch, + getState: () => AppState +) => { + try { + const { + buckets: {list}, + cloud: { + limits: { + buckets: {maxAllowed}, + }, + }, + } = getState() + + const bucketsCount = list.length + + if (maxAllowed <= bucketsCount) { + dispatch(setBucketLimitStatus(LimitStatus.EXCEEDED)) + dispatch(notify(resourceLimitReached('buckets'))) + } else { + dispatch(setBucketLimitStatus(LimitStatus.OK)) + } + } catch (e) { + console.error(e) + } +} diff --git a/ui/src/cloud/reducers/limits.ts b/ui/src/cloud/reducers/limits.ts index 008628e5e0..67ce1afca8 100644 --- a/ui/src/cloud/reducers/limits.ts +++ b/ui/src/cloud/reducers/limits.ts @@ -57,6 +57,10 @@ export const limitsReducer = ( draftState.dashboards.limitStatus = action.payload.limitStatus return } + case ActionTypes.SetBucketLimitStatus: { + draftState.buckets.limitStatus = action.payload.limitStatus + return + } } })