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 commentspull/19153/head
parent
48814abab1
commit
bc4c19dfd8
|
@ -3,20 +3,21 @@
|
|||
### Breaking
|
||||
|
||||
1. [19066](https://github.com/influxdata/influxdb/pull/19066): Drop deprecated /packages route tree
|
||||
1. [19116](https://github.com/influxdata/influxdb/pull/19116): Support more types for template envRef default value and require explicit default values
|
||||
1. [19116](https://github.com/influxdata/influxdb/pull/19116): Support more types for template envRef default value and require explicit default values
|
||||
|
||||
### 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. [19103](https://github.com/influxdata/influxdb/pull/19103): Enhance resource creation experience when limits are reached
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
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]
|
||||
|
||||
### Breaking
|
||||
|
||||
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
|
||||
|
||||
|
@ -41,7 +42,6 @@
|
|||
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
|
||||
|
||||
|
||||
## v2.0.0-beta.14 [2020-07-08]
|
||||
|
||||
### Features
|
||||
|
|
|
@ -3,8 +3,8 @@ import React, {FC, ReactChild, useState} from 'react'
|
|||
import {connect} from 'react-redux'
|
||||
|
||||
// Types
|
||||
import {AppState, ResourceType} from 'src/types'
|
||||
import {LimitStatus} from 'src/cloud/actions/limits'
|
||||
import {AppState, ResourceType, ColumnTypes} from 'src/types'
|
||||
import {LimitStatus, MonitoringLimits} from 'src/cloud/actions/limits'
|
||||
|
||||
// Components
|
||||
import {
|
||||
|
@ -20,14 +20,17 @@ import {
|
|||
ComponentColor,
|
||||
} from '@influxdata/clockface'
|
||||
import AssetLimitAlert from 'src/cloud/components/AssetLimitAlert'
|
||||
import AssetLimitButton from 'src/cloud/components/AssetLimitButton'
|
||||
|
||||
// Utils
|
||||
import {extractMonitoringLimitStatus} from 'src/cloud/utils/limits'
|
||||
import {
|
||||
extractChecksLimits,
|
||||
extractRulesLimits,
|
||||
extractEndpointsLimits,
|
||||
} from 'src/cloud/utils/limits'
|
||||
|
||||
type ColumnTypes =
|
||||
| ResourceType.NotificationRules
|
||||
| ResourceType.NotificationEndpoints
|
||||
| ResourceType.Checks
|
||||
// Constants
|
||||
import {CLOUD} from 'src/shared/constants'
|
||||
|
||||
interface OwnProps {
|
||||
type: ColumnTypes
|
||||
|
@ -38,7 +41,7 @@ interface OwnProps {
|
|||
}
|
||||
|
||||
interface StateProps {
|
||||
limitStatus: LimitStatus
|
||||
limitStatus: MonitoringLimits
|
||||
}
|
||||
|
||||
const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
|
||||
|
@ -53,6 +56,20 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
|
|||
|
||||
const formattedTitle = title.toLowerCase().replace(' ', '-')
|
||||
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 (
|
||||
<Panel
|
||||
|
@ -70,7 +87,7 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
|
|||
tooltipContents={questionMarkTooltipContents}
|
||||
/>
|
||||
</FlexBox>
|
||||
{createButton}
|
||||
{isLimitExceeded ? assetLimitButton : createButton}
|
||||
</Panel.Header>
|
||||
<div className="alerting-index--search">
|
||||
<Input
|
||||
|
@ -88,7 +105,10 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
|
|||
>
|
||||
<div className="alerting-index--list">
|
||||
{children(searchTerm)}
|
||||
<AssetLimitAlert resourceName={title} limitStatus={limitStatus} />
|
||||
<AssetLimitAlert
|
||||
resourceName={title}
|
||||
limitStatus={limitStatus[type]}
|
||||
/>
|
||||
</div>
|
||||
</DapperScrollbars>
|
||||
</div>
|
||||
|
@ -98,7 +118,11 @@ const AlertsColumnHeader: FC<OwnProps & StateProps> = ({
|
|||
|
||||
const mstp = ({cloud: {limits}}: AppState) => {
|
||||
return {
|
||||
limitStatus: extractMonitoringLimitStatus(limits),
|
||||
limitStatus: {
|
||||
[ResourceType.Checks]: extractChecksLimits(limits),
|
||||
[ResourceType.NotificationRules]: extractRulesLimits(limits),
|
||||
[ResourceType.NotificationEndpoints]: extractEndpointsLimits(limits),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,8 @@ import React, {FC, useEffect} from 'react'
|
|||
import {connect, ConnectedProps, useDispatch} from 'react-redux'
|
||||
|
||||
// Components
|
||||
import {
|
||||
Button,
|
||||
IconFont,
|
||||
ComponentColor,
|
||||
ComponentStatus,
|
||||
} from '@influxdata/clockface'
|
||||
import {Button, IconFont, ComponentColor} from '@influxdata/clockface'
|
||||
import AssetLimitButton from 'src/cloud/components/AssetLimitButton'
|
||||
|
||||
// Actions
|
||||
import {checkBucketLimits, LimitStatus} from 'src/cloud/actions/limits'
|
||||
|
@ -20,10 +16,12 @@ import {extractBucketLimits} from 'src/cloud/utils/limits'
|
|||
// Types
|
||||
import {AppState} from 'src/types'
|
||||
|
||||
type ReduxProps = ConnectedProps<typeof connector>
|
||||
type Props = ReduxProps
|
||||
// Constants
|
||||
import {CLOUD} from 'src/shared/constants'
|
||||
|
||||
const CreateBucketButton: FC<Props> = ({
|
||||
type ReduxProps = ConnectedProps<typeof connector>
|
||||
|
||||
const CreateBucketButton: FC<ReduxProps> = ({
|
||||
limitStatus,
|
||||
onShowOverlay,
|
||||
onDismissOverlay,
|
||||
|
@ -34,33 +32,22 @@ const CreateBucketButton: FC<Props> = ({
|
|||
dispatch(checkBucketLimits())
|
||||
}, [dispatch])
|
||||
|
||||
const limitExceeded = limitStatus === LimitStatus.EXCEEDED
|
||||
const text = 'Create Bucket'
|
||||
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 => {
|
||||
onShowOverlay('create-bucket', null, onDismissOverlay)
|
||||
}
|
||||
|
||||
const handleItemClick = (): void => {
|
||||
if (limitExceeded) {
|
||||
return
|
||||
}
|
||||
|
||||
onShowOverlay('create-bucket', null, onDismissOverlay)
|
||||
if (CLOUD && limitStatus === LimitStatus.EXCEEDED) {
|
||||
return <AssetLimitButton resourceName="Bucket" />
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={IconFont.Plus}
|
||||
color={ComponentColor.Primary}
|
||||
text={text}
|
||||
titleText={titleText}
|
||||
text="Create Bucket"
|
||||
titleText="Click to create a bucket"
|
||||
onClick={handleItemClick}
|
||||
testID="Create Bucket"
|
||||
status={buttonStatus}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,9 @@ import {
|
|||
ResourceType,
|
||||
} from 'src/types'
|
||||
|
||||
// Utils
|
||||
import {extractChecksLimits} from 'src/cloud/utils/limits'
|
||||
|
||||
type ReduxProps = ConnectedProps<typeof connector>
|
||||
type Props = ReduxProps & RouteComponentProps<{orgID: string}>
|
||||
|
||||
|
@ -34,6 +37,7 @@ const ChecksColumn: FunctionComponent<Props> = ({
|
|||
},
|
||||
rules,
|
||||
endpoints,
|
||||
limitStatus,
|
||||
}) => {
|
||||
const handleCreateThreshold = () => {
|
||||
history.push(`/orgs/${orgID}/alerting/checks/new-threshold`)
|
||||
|
@ -66,6 +70,7 @@ const ChecksColumn: FunctionComponent<Props> = ({
|
|||
|
||||
const createButton = (
|
||||
<CreateCheckDropdown
|
||||
limitStatus={limitStatus}
|
||||
onCreateThreshold={handleCreateThreshold}
|
||||
onCreateDeadman={handleCreateDeadman}
|
||||
/>
|
||||
|
@ -92,6 +97,10 @@ const ChecksColumn: FunctionComponent<Props> = ({
|
|||
}
|
||||
|
||||
const mstp = (state: AppState) => {
|
||||
const {
|
||||
cloud: {limits},
|
||||
} = state
|
||||
|
||||
const checks = getAll<Check>(state, ResourceType.Checks)
|
||||
|
||||
const endpoints = getAll<NotificationEndpoint>(
|
||||
|
@ -108,6 +117,7 @@ const mstp = (state: AppState) => {
|
|||
checks: sortChecksByName(checks),
|
||||
rules: sortRulesByName(rules),
|
||||
endpoints: sortEndpointsByName(endpoints),
|
||||
limitStatus: extractChecksLimits(limits),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,41 @@
|
|||
// Libraries
|
||||
import React, {FunctionComponent, MouseEvent} from 'react'
|
||||
import {connect, ConnectedProps} from 'react-redux'
|
||||
|
||||
// Components
|
||||
import {Dropdown, ComponentColor, IconFont} from '@influxdata/clockface'
|
||||
|
||||
// Actions
|
||||
import {showOverlay, dismissOverlay} from 'src/overlays/actions/overlays'
|
||||
|
||||
// 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
|
||||
onCreateDeadman: () => void
|
||||
limitStatus: LimitStatus
|
||||
}
|
||||
|
||||
const CreateCheckDropdown: FunctionComponent<Props> = ({
|
||||
type ReduxProps = ConnectedProps<typeof connector>
|
||||
|
||||
const CreateCheckDropdown: FunctionComponent<OwnProps & ReduxProps> = ({
|
||||
onCreateThreshold,
|
||||
onCreateDeadman,
|
||||
onDismissOverlay,
|
||||
onShowOverlay,
|
||||
limitStatus,
|
||||
}) => {
|
||||
const handleItemClick = (type: CheckType): void => {
|
||||
if (CLOUD && limitStatus === LimitStatus.EXCEEDED) {
|
||||
onShowOverlay('asset-limit', {asset: 'Checks'}, onDismissOverlay)
|
||||
return
|
||||
}
|
||||
|
||||
if (type === 'threshold') {
|
||||
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)
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
Limits,
|
||||
RemoteDataState,
|
||||
ResourceType,
|
||||
ColumnTypes,
|
||||
} from 'src/types'
|
||||
|
||||
// Selectors
|
||||
|
@ -36,6 +37,10 @@ export enum LimitStatus {
|
|||
EXCEEDED = 'exceeded',
|
||||
}
|
||||
|
||||
export type MonitoringLimits = {
|
||||
[type in ColumnTypes]: LimitStatus
|
||||
}
|
||||
|
||||
export enum ActionTypes {
|
||||
SetLimits = 'SET_LIMITS',
|
||||
SetLimitsStatus = 'SET_LIMITS_STATUS',
|
||||
|
|
|
@ -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)
|
|
@ -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>
|
||||
You’ve 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)
|
|
@ -34,10 +34,16 @@ export const extractTaskMax = (limits: LimitsState): number => {
|
|||
return get(limits, 'tasks.maxAllowed') || Infinity
|
||||
}
|
||||
|
||||
export const extractChecksLimits = (limits: LimitsState): LimitStatus => {
|
||||
return get(limits, 'checks.limitStatus')
|
||||
}
|
||||
export const extractChecksMax = (limits: LimitsState): number => {
|
||||
return get(limits, 'checks.maxAllowed') || Infinity
|
||||
}
|
||||
|
||||
export const extractRulesLimits = (limits: LimitsState): LimitStatus => {
|
||||
return get(limits, 'rules.limitStatus')
|
||||
}
|
||||
export const extractRulesMax = (limits: LimitsState): number => {
|
||||
return get(limits, 'rules.maxAllowed') || Infinity
|
||||
}
|
||||
|
@ -45,6 +51,9 @@ export const extractBlockedRules = (limits: LimitsState): string[] => {
|
|||
return get(limits, 'rules.blocked') || []
|
||||
}
|
||||
|
||||
export const extractEndpointsLimits = (limits: LimitsState): LimitStatus => {
|
||||
return get(limits, 'endpoints.limitStatus')
|
||||
}
|
||||
export const extractEndpointsMax = (limits: LimitsState): number => {
|
||||
return get(limits, 'endpoints.maxAllowed') || Infinity
|
||||
}
|
||||
|
@ -52,22 +61,6 @@ export const extractBlockedEndpoints = (limits: LimitsState): string[] => {
|
|||
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 = (
|
||||
limits: LimitsState
|
||||
): string => {
|
||||
|
|
|
@ -29,8 +29,7 @@ import {setDashboardSort} from 'src/dashboards/actions/creators'
|
|||
|
||||
// Types
|
||||
import {AppState, ResourceType} from 'src/types'
|
||||
import {LimitStatus} from 'src/cloud/actions/limits'
|
||||
import {ComponentStatus, Sort} from '@influxdata/clockface'
|
||||
import {Sort} from '@influxdata/clockface'
|
||||
import {SortTypes} from 'src/shared/utils/sort'
|
||||
import {DashboardSortKey} from 'src/shared/components/resource_sort_dropdown/generateSortItems'
|
||||
|
||||
|
@ -52,7 +51,7 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {createDashboard, sortOptions} = this.props
|
||||
const {createDashboard, sortOptions, limitStatus} = this.props
|
||||
const {searchTerm} = this.state
|
||||
|
||||
return (
|
||||
|
@ -87,7 +86,7 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
onSelectTemplate={this.summonImportFromTemplateOverlay}
|
||||
resourceName="Dashboard"
|
||||
canImportFromTemplate={true}
|
||||
status={this.addResourceStatus}
|
||||
limitStatus={limitStatus}
|
||||
/>
|
||||
</Page.ControlBarRight>
|
||||
</Page.ControlBar>
|
||||
|
@ -156,14 +155,6 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
} = this.props
|
||||
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) => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import TelegrafConfigOverlay from 'src/telegrafs/components/TelegrafConfigOverla
|
|||
import TelegrafOutputOverlay from 'src/telegrafs/components/TelegrafOutputOverlay'
|
||||
import OrgSwitcherOverlay from 'src/pageLayout/components/OrgSwitcherOverlay'
|
||||
import CreateBucketOverlay from 'src/buckets/components/CreateBucketOverlay'
|
||||
import AssetLimitOverlay from 'src/cloud/components/AssetLimitOverlay'
|
||||
|
||||
// Actions
|
||||
import {dismissOverlay} from 'src/overlays/actions/overlays'
|
||||
|
@ -57,6 +58,9 @@ const OverlayController: FunctionComponent<OverlayControllerProps> = props => {
|
|||
case 'create-bucket':
|
||||
activeOverlay = <CreateBucketOverlay onClose={closer} />
|
||||
break
|
||||
case 'asset-limit':
|
||||
activeOverlay = <AssetLimitOverlay onClose={closer} />
|
||||
break
|
||||
default:
|
||||
visibility = false
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ export type OverlayID =
|
|||
| 'telegraf-output'
|
||||
| 'switch-organizations'
|
||||
| 'create-bucket'
|
||||
| 'asset-limit'
|
||||
|
||||
export interface OverlayParams {
|
||||
[key: string]: string
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {connect, ConnectedProps} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
|
@ -11,11 +12,21 @@ import {
|
|||
ComponentStatus,
|
||||
} 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 {
|
||||
onSelectNew: () => void
|
||||
onSelectImport: () => void
|
||||
onSelectTemplate?: () => void
|
||||
resourceName: string
|
||||
limitStatus?: LimitStatus
|
||||
}
|
||||
|
||||
interface DefaultProps {
|
||||
|
@ -24,9 +35,11 @@ interface DefaultProps {
|
|||
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 = {
|
||||
canImportFromTemplate: false,
|
||||
status: ComponentStatus.Default,
|
||||
|
@ -121,8 +134,23 @@ export default class AddResourceDropdown extends PureComponent<Props> {
|
|||
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 => {
|
||||
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) {
|
||||
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)
|
||||
|
|
|
@ -30,10 +30,12 @@ interface StateProps {
|
|||
interface OwnProps {
|
||||
className?: string
|
||||
buttonText?: string
|
||||
size?: ComponentSize
|
||||
}
|
||||
|
||||
const CloudUpgradeButton: FC<StateProps & OwnProps> = ({
|
||||
inView,
|
||||
size = ComponentSize.Small,
|
||||
className,
|
||||
buttonText = 'Upgrade Now',
|
||||
}) => {
|
||||
|
@ -47,7 +49,7 @@ const CloudUpgradeButton: FC<StateProps & OwnProps> = ({
|
|||
<LinkButton
|
||||
className={cloudUpgradeButtonClass}
|
||||
color={ComponentColor.Success}
|
||||
size={ComponentSize.Small}
|
||||
size={size}
|
||||
shape={ButtonShape.Default}
|
||||
href={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
|
||||
target="_self"
|
||||
|
|
|
@ -1,67 +1,119 @@
|
|||
.load-data--asset-alert {
|
||||
margin-bottom: $cf-marg-d;
|
||||
margin-bottom: $cf-marg-d;
|
||||
}
|
||||
|
||||
.cf-page-control-bar {
|
||||
+ .rate-alert {
|
||||
margin: 0 $cf-marg-c $cf-marg-c;
|
||||
}
|
||||
+ .rate-alert {
|
||||
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;
|
||||
}
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
font-weight: 500;
|
||||
|
||||
.rate-alert--button {
|
||||
margin-top: $cf-marg-b;
|
||||
}
|
||||
}
|
||||
|
||||
.cf-page--title {
|
||||
flex-shrink: 0;
|
||||
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');
|
||||
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;
|
||||
}
|
||||
.asset-alert--contents {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
.rate-alert {
|
||||
margin-left: $cf-marg-d;
|
||||
}
|
||||
.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--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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.cf-page-control-bar {
|
||||
+ .rate-alert {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
InputLabel,
|
||||
SlideToggle,
|
||||
ComponentSize,
|
||||
ComponentStatus,
|
||||
Page,
|
||||
Sort,
|
||||
FlexBox,
|
||||
|
@ -57,6 +56,7 @@ export default class TasksHeader extends PureComponent<Props> {
|
|||
sortType,
|
||||
sortDirection,
|
||||
onSort,
|
||||
limitStatus,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
|
@ -98,19 +98,11 @@ export default class TasksHeader extends PureComponent<Props> {
|
|||
onSelectImport={onImportTask}
|
||||
onSelectTemplate={onImportFromTemplate}
|
||||
resourceName="Task"
|
||||
status={this.addResourceStatus}
|
||||
limitStatus={limitStatus}
|
||||
/>
|
||||
</Page.ControlBarRight>
|
||||
</Page.ControlBar>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
private get addResourceStatus(): ComponentStatus {
|
||||
const {limitStatus} = this.props
|
||||
if (limitStatus === LimitStatus.EXCEEDED) {
|
||||
return ComponentStatus.Disabled
|
||||
}
|
||||
return ComponentStatus.Default
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ import {
|
|||
CheckBase as GenCheckBase,
|
||||
NotificationEndpointBase as GenEndpointBase,
|
||||
} from 'src/client'
|
||||
|
||||
import {RemoteDataState} from 'src/types'
|
||||
import {ResourceType} from './resources'
|
||||
|
||||
type Omit<T, U> = Pick<T, Exclude<keyof T, U>>
|
||||
type Overwrite<T, U> = Omit<T, keyof U> & U
|
||||
|
@ -30,6 +32,11 @@ interface WithClientID<T> {
|
|||
value: T
|
||||
}
|
||||
|
||||
export type ColumnTypes =
|
||||
| ResourceType.NotificationRules
|
||||
| ResourceType.NotificationEndpoints
|
||||
| ResourceType.Checks
|
||||
|
||||
/* Endpoints */
|
||||
type EndpointOverrides = {
|
||||
status: RemoteDataState
|
||||
|
|
Loading…
Reference in New Issue