diff --git a/ui/src/dashboards/actions/index.ts b/ui/src/dashboards/actions/index.ts index 664d0d61f1..6509982109 100644 --- a/ui/src/dashboards/actions/index.ts +++ b/ui/src/dashboards/actions/index.ts @@ -23,10 +23,6 @@ import { updateTimeRangeFromQueryParams, DeleteTimeRangeAction, } from 'src/dashboards/actions/ranges' -import { - importDashboardSucceeded, - importDashboardFailed, -} from 'src/shared/copy/notifications' import {setView, SetViewAction, setViews} from 'src/dashboards/actions/views' import { getVariables, @@ -34,6 +30,7 @@ import { selectValue, } from 'src/variables/actions' import {setExportTemplate} from 'src/templates/actions' +import {checkDashboardLimits} from 'src/cloud/actions/limits' // Utils import {filterUnusedVars} from 'src/shared/utils/filterUnusedVars' @@ -246,14 +243,15 @@ export const createDashboardFromTemplate = ( const dashboards = await getDashboardsAJAX(org.id) dispatch(setDashboards(RemoteDataState.Done, dashboards)) - dispatch(notify(importDashboardSucceeded())) + dispatch(notify(copy.importDashboardSucceeded())) + dispatch(checkDashboardLimits()) } catch (error) { - dispatch(notify(importDashboardFailed(error))) + dispatch(notify(copy.importDashboardFailed(error))) } } export const deleteDashboardAsync = (dashboard: Dashboard) => async ( - dispatch: Dispatch + dispatch ): Promise => { dispatch(removeDashboard(dashboard.id)) dispatch(deleteTimeRange(dashboard.id)) @@ -261,6 +259,7 @@ export const deleteDashboardAsync = (dashboard: Dashboard) => async ( try { await deleteDashboardAJAX(dashboard) dispatch(notify(copy.dashboardDeleted(dashboard.name))) + dispatch(checkDashboardLimits()) } catch (error) { dispatch( notify(copy.dashboardDeleteFailed(dashboard.name, error.data.message)) diff --git a/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx b/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx index 11e3467dda..07ef0c5177 100644 --- a/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx +++ b/ui/src/dashboards/components/dashboard_index/DashboardsIndex.tsx @@ -3,47 +3,48 @@ import React, {PureComponent} from 'react' import {InjectedRouter} from 'react-router' import {connect} from 'react-redux' +// Decorators +import {ErrorHandling} from 'src/shared/decorators/errors' + // Components import DashboardsIndexContents from 'src/dashboards/components/dashboard_index/DashboardsIndexContents' import {Page} from 'src/pageLayout' import SearchWidget from 'src/shared/components/search_widget/SearchWidget' import AddResourceDropdown from 'src/shared/components/AddResourceDropdown' import PageTitleWithOrg from 'src/shared/components/PageTitleWithOrg' +import GetAssetLimits from 'src/cloud/components/GetAssetLimits' +import GetResources, {ResourceTypes} from 'src/shared/components/GetResources' // APIs import {createDashboard, cloneDashboard} from 'src/dashboards/apis/' // Actions import { - getDashboardsAsync, deleteDashboardAsync, updateDashboardAsync, } from 'src/dashboards/actions' -import {retainRangesDashTimeV1 as retainRangesDashTimeV1Action} from 'src/dashboards/actions/ranges' import {notify as notifyAction} from 'src/shared/actions/notifications' -import GetResources, {ResourceTypes} from 'src/shared/components/GetResources' +import {checkDashboardLimits as checkDashboardLimitsAction} from 'src/cloud/actions/limits' // Constants import {DEFAULT_DASHBOARD_NAME} from 'src/dashboards/constants/index' import {dashboardCreateFailed} from 'src/shared/copy/notifications' // Types -import {Notification} from 'src/types/notifications' import {Dashboard, AppState} from 'src/types' - -// Decorators -import {ErrorHandling} from 'src/shared/decorators/errors' +import {LimitStatus} from 'src/cloud/actions/limits' +import {ComponentStatus} from 'src/clockface' interface DispatchProps { - handleGetDashboards: typeof getDashboardsAsync handleDeleteDashboard: typeof deleteDashboardAsync handleUpdateDashboard: typeof updateDashboardAsync - notify: (message: Notification) => void - retainRangesDashTimeV1: (dashboardIDs: string[]) => void + checkDashboardLimits: typeof checkDashboardLimitsAction + notify: typeof notifyAction } interface StateProps { dashboards: Dashboard[] + limitStatus: LimitStatus } interface OwnProps { @@ -67,15 +68,8 @@ class DashboardIndex extends PureComponent { } } - public async componentDidMount() { - const {dashboards} = this.props - - const dashboardIDs = dashboards.map(d => d.id) - this.props.retainRangesDashTimeV1(dashboardIDs) - } - public render() { - const {dashboards, notify, handleUpdateDashboard} = this.props + const {handleUpdateDashboard, handleDeleteDashboard} = this.props const {searchTerm} = this.state return ( @@ -92,30 +86,33 @@ class DashboardIndex extends PureComponent { onSelectTemplate={this.summonImportFromTemplateOverlay} resourceName="Dashboard" canImportFromTemplate={true} + status={this.addResourceStatus} />
- ( - + + ( + + )} + onDeleteDashboard={handleDeleteDashboard} + onCreateDashboard={this.handleCreateDashboard} + onCloneDashboard={this.handleCloneDashboard} + onUpdateDashboard={handleUpdateDashboard} searchTerm={searchTerm} + onFilterChange={this.handleFilterDashboards} + onImportDashboard={this.summonImportOverlay} /> - )} - dashboards={dashboards} - onDeleteDashboard={this.handleDeleteDashboard} - onCreateDashboard={this.handleCreateDashboard} - onCloneDashboard={this.handleCloneDashboard} - onUpdateDashboard={handleUpdateDashboard} - notify={notify} - searchTerm={searchTerm} - onFilterChange={this.handleFilterDashboards} - onImportDashboard={this.summonImportOverlay} - /> + +
@@ -130,6 +127,7 @@ class DashboardIndex extends PureComponent { router, notify, params: {orgID}, + checkDashboardLimits, } = this.props try { const newDashboard = { @@ -138,6 +136,7 @@ class DashboardIndex extends PureComponent { orgID, } const data = await createDashboard(newDashboard) + checkDashboardLimits() router.push(`/orgs/${orgID}/dashboards/${data.id}`) } catch (error) { notify(dashboardCreateFailed()) @@ -152,6 +151,7 @@ class DashboardIndex extends PureComponent { notify, dashboards, params: {orgID}, + checkDashboardLimits, } = this.props try { const data = await cloneDashboard( @@ -163,16 +163,13 @@ class DashboardIndex extends PureComponent { orgID ) router.push(`/orgs/${orgID}/dashboards/${data.id}`) + checkDashboardLimits() } catch (error) { console.error(error) notify(dashboardCreateFailed()) } } - private handleDeleteDashboard = (dashboard: Dashboard) => { - this.props.handleDeleteDashboard(dashboard) - } - private handleFilterDashboards = (searchTerm: string): void => { this.setState({searchTerm}) } @@ -192,24 +189,38 @@ class DashboardIndex extends PureComponent { } = this.props router.push(`/orgs/${orgID}/dashboards/import/template`) } + + private get addResourceStatus(): ComponentStatus { + const {limitStatus} = this.props + if (limitStatus === LimitStatus.EXCEEDED) { + return ComponentStatus.Disabled + } else { + return ComponentStatus.Default + } + } } const mstp = (state: AppState): StateProps => { const { dashboards: {list: dashboards}, + cloud: { + limits: { + dashboards: {limitStatus}, + }, + }, } = state return { dashboards, + limitStatus, } } const mdtp: DispatchProps = { notify: notifyAction, - handleGetDashboards: getDashboardsAsync, handleDeleteDashboard: deleteDashboardAsync, handleUpdateDashboard: updateDashboardAsync, - retainRangesDashTimeV1: retainRangesDashTimeV1Action, + checkDashboardLimits: checkDashboardLimitsAction, } export default connect( diff --git a/ui/src/dashboards/components/dashboard_index/DashboardsIndexContents.tsx b/ui/src/dashboards/components/dashboard_index/DashboardsIndexContents.tsx index 4adb03477a..872b9d0e21 100644 --- a/ui/src/dashboards/components/dashboard_index/DashboardsIndexContents.tsx +++ b/ui/src/dashboards/components/dashboard_index/DashboardsIndexContents.tsx @@ -1,34 +1,55 @@ // Libraries import React, {Component} from 'react' +import {connect} from 'react-redux' import _ from 'lodash' // Components import Table from 'src/dashboards/components/dashboard_index/Table' import FilterList from 'src/shared/components/Filter' -import GetResources, {ResourceTypes} from 'src/shared/components/GetResources' + +// Actions +import {retainRangesDashTimeV1 as retainRangesDashTimeV1Action} from 'src/dashboards/actions/ranges' +import {checkDashboardLimits as checkDashboardLimitsAction} from 'src/cloud/actions/limits' // Decorators import {ErrorHandling} from 'src/shared/decorators/errors' // Types -import {Dashboard} from 'src/types' -import {Notification} from 'src/types/notifications' +import {Dashboard, AppState, RemoteDataState} from 'src/types' -interface Props { - dashboards: Dashboard[] +interface OwnProps { onCreateDashboard: () => void onCloneDashboard: (dashboard: Dashboard) => void onDeleteDashboard: (dashboard: Dashboard) => void onUpdateDashboard: (dashboard: Dashboard) => void onFilterChange: (searchTerm: string) => void - notify: (message: Notification) => void searchTerm: string filterComponent?: () => JSX.Element onImportDashboard: () => void } +interface DispatchProps { + retainRangesDashTimeV1: typeof retainRangesDashTimeV1Action + checkDashboardLimits: typeof checkDashboardLimitsAction +} + +interface StateProps { + dashboards: Dashboard[] + limitStatus: RemoteDataState +} + +type Props = DispatchProps & StateProps & OwnProps + @ErrorHandling -export default class DashboardsIndexContents extends Component { +class DashboardsIndexContents extends Component { + public async componentDidMount() { + const {dashboards} = this.props + + const dashboardIDs = dashboards.map(d => d.id) + this.props.retainRangesDashTimeV1(dashboardIDs) + this.props.checkDashboardLimits() + } + public render() { const { onDeleteDashboard, @@ -43,28 +64,50 @@ export default class DashboardsIndexContents extends Component { } = this.props return ( - - - list={dashboards} - searchTerm={searchTerm} - searchKeys={['name', 'labels[].name']} - sortByKey="name" - > - {filteredDashboards => ( - - )} - - + + list={dashboards} + searchTerm={searchTerm} + searchKeys={['name', 'labels[].name']} + sortByKey="name" + > + {filteredDashboards => ( +
+ )} + ) } } + +const mstp = (state: AppState): StateProps => { + const { + dashboards: {list: dashboards}, + cloud: { + limits: {status}, + }, + } = state + + return { + dashboards, + limitStatus: status, + } +} + +const mdtp: DispatchProps = { + retainRangesDashTimeV1: retainRangesDashTimeV1Action, + checkDashboardLimits: checkDashboardLimitsAction, +} + +export default connect( + mstp, + mdtp +)(DashboardsIndexContents) diff --git a/ui/src/shared/components/AddResourceDropdown.tsx b/ui/src/shared/components/AddResourceDropdown.tsx index d70c2d33ee..ba14c4871c 100644 --- a/ui/src/shared/components/AddResourceDropdown.tsx +++ b/ui/src/shared/components/AddResourceDropdown.tsx @@ -3,7 +3,7 @@ import React, {PureComponent} from 'react' import _ from 'lodash' // Components -import {Dropdown, DropdownMode} from 'src/clockface' +import {Dropdown, DropdownMode, ComponentStatus} from 'src/clockface' // Types import {IconFont, ComponentColor, ComponentSize} from '@influxdata/clockface' @@ -14,10 +14,12 @@ interface OwnProps { onSelectTemplate?: () => void resourceName: string canImportFromTemplate?: boolean + status?: ComponentStatus } interface DefaultProps { canImportFromTemplate: boolean + status: ComponentStatus } type Props = OwnProps & DefaultProps @@ -25,6 +27,7 @@ type Props = OwnProps & DefaultProps export default class AddResourceDropdown extends PureComponent { public static defaultProps: DefaultProps = { canImportFromTemplate: false, + status: ComponentStatus.Default, } public render() { @@ -37,6 +40,7 @@ export default class AddResourceDropdown extends PureComponent { buttonSize={ComponentSize.Small} widthPixels={160} onChange={this.handleSelect} + status={this.props.status} > {this.optionItems}