feat(ui): hide the "Upgrade Now" button for Cloud users when appropriate
parent
7f8aa4c40f
commit
4e0672e78e
|
@ -0,0 +1,48 @@
|
|||
// API
|
||||
import {getOrgSettings as getOrgSettingsAJAX} from 'src/cloud/apis/orgsettings'
|
||||
|
||||
// Constants
|
||||
import {FREE_ORG_HIDE_UPGRADE_SETTING} from 'src/cloud/constants'
|
||||
|
||||
// Types
|
||||
import {GetState, OrgSetting} from 'src/types'
|
||||
|
||||
// Selectors
|
||||
import {getOrg} from 'src/organizations/selectors'
|
||||
|
||||
export enum ActionTypes {
|
||||
SetOrgSettings = 'SET_ORG_SETTINGS',
|
||||
}
|
||||
|
||||
export type Actions = SetOrgSettings
|
||||
|
||||
export interface SetOrgSettings {
|
||||
type: ActionTypes.SetOrgSettings
|
||||
payload: {orgSettings: OrgSetting[]}
|
||||
}
|
||||
|
||||
export const setOrgSettings = (settings: OrgSetting[]): SetOrgSettings => {
|
||||
return {
|
||||
type: ActionTypes.SetOrgSettings,
|
||||
payload: {orgSettings: settings},
|
||||
}
|
||||
}
|
||||
|
||||
export const setFreeOrgSettings = (): SetOrgSettings => {
|
||||
return {
|
||||
type: ActionTypes.SetOrgSettings,
|
||||
payload: {orgSettings: [FREE_ORG_HIDE_UPGRADE_SETTING]},
|
||||
}
|
||||
}
|
||||
|
||||
export const getOrgSettings = () => async (dispatch, getState: GetState) => {
|
||||
try {
|
||||
const org = getOrg(getState())
|
||||
|
||||
const result = await getOrgSettingsAJAX(org.id)
|
||||
dispatch(setOrgSettings(result.settings))
|
||||
} catch (error) {
|
||||
dispatch(setFreeOrgSettings())
|
||||
console.error(error)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import AJAX from 'src/utils/ajax'
|
||||
import {OrgSettings} from 'src/types'
|
||||
|
||||
export const getOrgSettings = async (orgID: string): Promise<OrgSettings> => {
|
||||
try {
|
||||
const {data} = await AJAX({
|
||||
method: 'GET',
|
||||
url: `/api/v2private/orgs/${orgID}/settings`,
|
||||
})
|
||||
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Libraries
|
||||
import {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
// Constants
|
||||
import {CLOUD} from 'src/shared/constants'
|
||||
|
||||
// Actions
|
||||
import {getOrgSettings as getOrgSettingsAction} from 'src/cloud/actions/orgsettings'
|
||||
|
||||
interface DispatchProps {
|
||||
getOrgSettings: typeof getOrgSettingsAction
|
||||
}
|
||||
|
||||
class OrgSettings extends PureComponent<DispatchProps> {
|
||||
public componentDidMount() {
|
||||
if (CLOUD) {
|
||||
this.props.getOrgSettings()
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
getOrgSettings: getOrgSettingsAction,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps, {}>(
|
||||
null,
|
||||
mdtp
|
||||
)(OrgSettings)
|
|
@ -25,3 +25,13 @@ export const DemoDataTemplates = {
|
|||
DemoDataDashboards[WebsiteMonitoringBucket]
|
||||
),
|
||||
}
|
||||
|
||||
export const HIDE_UPGRADE_CTA_KEY = 'hide_upgrade_cta'
|
||||
export const FREE_ORG_HIDE_UPGRADE_SETTING = {
|
||||
key: HIDE_UPGRADE_CTA_KEY,
|
||||
value: 'false',
|
||||
}
|
||||
export const PAID_ORG_HIDE_UPGRADE_SETTING = {
|
||||
key: HIDE_UPGRADE_CTA_KEY,
|
||||
value: 'true',
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import {produce} from 'immer'
|
||||
|
||||
import {Actions, ActionTypes} from 'src/cloud/actions/orgsettings'
|
||||
import {OrgSetting} from 'src/types'
|
||||
|
||||
import {PAID_ORG_HIDE_UPGRADE_SETTING} from 'src/cloud/constants'
|
||||
|
||||
export interface OrgSettingsState {
|
||||
settings: OrgSetting[]
|
||||
}
|
||||
|
||||
export const defaultState: OrgSettingsState = {
|
||||
settings: [PAID_ORG_HIDE_UPGRADE_SETTING],
|
||||
}
|
||||
|
||||
export const orgSettingsReducer = (
|
||||
state = defaultState,
|
||||
action: Actions
|
||||
): OrgSettingsState =>
|
||||
produce(state, draftState => {
|
||||
if (action.type === ActionTypes.SetOrgSettings) {
|
||||
const {orgSettings} = action.payload
|
||||
draftState.settings = orgSettings
|
||||
}
|
||||
return
|
||||
})
|
|
@ -11,16 +11,21 @@ import NavHeader from 'src/pageLayout/components/NavHeader'
|
|||
import CloudUpgradeNavBanner from 'src/shared/components/CloudUpgradeNavBanner'
|
||||
import CloudExclude from 'src/shared/components/cloud/CloudExclude'
|
||||
import CloudOnly from 'src/shared/components/cloud/CloudOnly'
|
||||
import OrgSettings from 'src/cloud/components/OrgSettings'
|
||||
import {FeatureFlag} from 'src/shared/utils/featureFlag'
|
||||
|
||||
// Constants
|
||||
import {generateNavItems} from 'src/pageLayout/constants/navigationHierarchy'
|
||||
import {
|
||||
HIDE_UPGRADE_CTA_KEY,
|
||||
PAID_ORG_HIDE_UPGRADE_SETTING,
|
||||
} from 'src/cloud/constants'
|
||||
|
||||
// Utils
|
||||
import {getNavItemActivation} from 'src/pageLayout/utils'
|
||||
|
||||
// Types
|
||||
import {AppState, NavBarState} from 'src/types'
|
||||
import {AppState, NavBarState, OrgSetting} from 'src/types'
|
||||
|
||||
// Actions
|
||||
import {setNavBarState} from 'src/shared/actions/app'
|
||||
|
@ -31,6 +36,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
|||
interface StateProps {
|
||||
isHidden: boolean
|
||||
navBarState: NavBarState
|
||||
showUpgradeButton: boolean
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -47,6 +53,7 @@ class TreeSidebar extends PureComponent<Props> {
|
|||
params: {orgID},
|
||||
navBarState,
|
||||
handleSetNavBarState,
|
||||
showUpgradeButton,
|
||||
} = this.props
|
||||
|
||||
if (isHidden) {
|
||||
|
@ -67,132 +74,134 @@ class TreeSidebar extends PureComponent<Props> {
|
|||
const navItems = generateNavItems(orgID)
|
||||
|
||||
return (
|
||||
<TreeNav
|
||||
expanded={isExpanded}
|
||||
headerElement={<NavHeader link={orgPrefix} />}
|
||||
userElement={<UserWidget />}
|
||||
onToggleClick={handleToggleNavExpansion}
|
||||
bannerElement={<CloudUpgradeNavBanner />}
|
||||
>
|
||||
{navItems.map(item => {
|
||||
const linkElement = (className: string): JSX.Element => {
|
||||
if (item.link.type === 'href') {
|
||||
return <a href={item.link.location} className={className} />
|
||||
}
|
||||
<OrgSettings>
|
||||
<TreeNav
|
||||
expanded={isExpanded}
|
||||
headerElement={<NavHeader link={orgPrefix} />}
|
||||
userElement={<UserWidget />}
|
||||
onToggleClick={handleToggleNavExpansion}
|
||||
bannerElement={showUpgradeButton ? <CloudUpgradeNavBanner /> : null}
|
||||
>
|
||||
{navItems.map(item => {
|
||||
const linkElement = (className: string): JSX.Element => {
|
||||
if (item.link.type === 'href') {
|
||||
return <a href={item.link.location} className={className} />
|
||||
}
|
||||
|
||||
return <Link to={item.link.location} className={className} />
|
||||
}
|
||||
let navItemElement = (
|
||||
<TreeNav.Item
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
testID={item.testID}
|
||||
icon={<Icon glyph={item.icon} />}
|
||||
label={item.label}
|
||||
shortLabel={item.shortLabel}
|
||||
active={getNavItemActivation(
|
||||
item.activeKeywords,
|
||||
location.pathname
|
||||
)}
|
||||
linkElement={linkElement}
|
||||
>
|
||||
{Boolean(item.menu) && (
|
||||
<TreeNav.SubMenu>
|
||||
{item.menu.map(menuItem => {
|
||||
const linkElement = (className: string): JSX.Element => {
|
||||
if (menuItem.link.type === 'href') {
|
||||
return (
|
||||
<a
|
||||
href={menuItem.link.location}
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return <Link to={item.link.location} className={className} />
|
||||
}
|
||||
let navItemElement = (
|
||||
<TreeNav.Item
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
testID={item.testID}
|
||||
icon={<Icon glyph={item.icon} />}
|
||||
label={item.label}
|
||||
shortLabel={item.shortLabel}
|
||||
active={getNavItemActivation(
|
||||
item.activeKeywords,
|
||||
location.pathname
|
||||
)}
|
||||
linkElement={linkElement}
|
||||
>
|
||||
{Boolean(item.menu) && (
|
||||
<TreeNav.SubMenu>
|
||||
{item.menu.map(menuItem => {
|
||||
const linkElement = (className: string): JSX.Element => {
|
||||
if (menuItem.link.type === 'href') {
|
||||
return (
|
||||
<a
|
||||
href={menuItem.link.location}
|
||||
<Link
|
||||
to={menuItem.link.location}
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={menuItem.link.location}
|
||||
className={className}
|
||||
let navSubItemElement = (
|
||||
<TreeNav.SubItem
|
||||
key={menuItem.id}
|
||||
id={menuItem.id}
|
||||
testID={menuItem.testID}
|
||||
active={getNavItemActivation(
|
||||
[menuItem.id],
|
||||
location.pathname
|
||||
)}
|
||||
label={menuItem.label}
|
||||
linkElement={linkElement}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
let navSubItemElement = (
|
||||
<TreeNav.SubItem
|
||||
key={menuItem.id}
|
||||
id={menuItem.id}
|
||||
testID={menuItem.testID}
|
||||
active={getNavItemActivation(
|
||||
[menuItem.id],
|
||||
location.pathname
|
||||
)}
|
||||
label={menuItem.label}
|
||||
linkElement={linkElement}
|
||||
/>
|
||||
)
|
||||
if (menuItem.cloudExclude) {
|
||||
navSubItemElement = (
|
||||
<CloudExclude key={menuItem.id}>
|
||||
{navSubItemElement}
|
||||
</CloudExclude>
|
||||
)
|
||||
}
|
||||
|
||||
if (menuItem.cloudExclude) {
|
||||
navSubItemElement = (
|
||||
<CloudExclude key={menuItem.id}>
|
||||
{navSubItemElement}
|
||||
</CloudExclude>
|
||||
)
|
||||
}
|
||||
if (menuItem.cloudOnly) {
|
||||
navSubItemElement = (
|
||||
<CloudOnly key={menuItem.id}>
|
||||
{navSubItemElement}
|
||||
</CloudOnly>
|
||||
)
|
||||
}
|
||||
|
||||
if (menuItem.cloudOnly) {
|
||||
navSubItemElement = (
|
||||
<CloudOnly key={menuItem.id}>
|
||||
{navSubItemElement}
|
||||
</CloudOnly>
|
||||
)
|
||||
}
|
||||
if (menuItem.featureFlag) {
|
||||
navSubItemElement = (
|
||||
<FeatureFlag
|
||||
key={menuItem.id}
|
||||
name={menuItem.featureFlag}
|
||||
equals={menuItem.featureFlagValue}
|
||||
>
|
||||
{navSubItemElement}
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
if (menuItem.featureFlag) {
|
||||
navSubItemElement = (
|
||||
<FeatureFlag
|
||||
key={menuItem.id}
|
||||
name={menuItem.featureFlag}
|
||||
equals={menuItem.featureFlagValue}
|
||||
>
|
||||
{navSubItemElement}
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
return navSubItemElement
|
||||
})}
|
||||
</TreeNav.SubMenu>
|
||||
)}
|
||||
</TreeNav.Item>
|
||||
)
|
||||
|
||||
if (item.cloudExclude) {
|
||||
navItemElement = (
|
||||
<CloudExclude key={item.id}>{navItemElement}</CloudExclude>
|
||||
return navSubItemElement
|
||||
})}
|
||||
</TreeNav.SubMenu>
|
||||
)}
|
||||
</TreeNav.Item>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.cloudOnly) {
|
||||
navItemElement = (
|
||||
<CloudOnly key={item.id}>{navItemElement}</CloudOnly>
|
||||
)
|
||||
}
|
||||
if (item.cloudExclude) {
|
||||
navItemElement = (
|
||||
<CloudExclude key={item.id}>{navItemElement}</CloudExclude>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.featureFlag) {
|
||||
navItemElement = (
|
||||
<FeatureFlag
|
||||
key={item.id}
|
||||
name={item.featureFlag}
|
||||
equals={item.featureFlagValue}
|
||||
>
|
||||
{navItemElement}
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
if (item.cloudOnly) {
|
||||
navItemElement = (
|
||||
<CloudOnly key={item.id}>{navItemElement}</CloudOnly>
|
||||
)
|
||||
}
|
||||
|
||||
return navItemElement
|
||||
})}
|
||||
</TreeNav>
|
||||
if (item.featureFlag) {
|
||||
navItemElement = (
|
||||
<FeatureFlag
|
||||
key={item.id}
|
||||
name={item.featureFlag}
|
||||
equals={item.featureFlagValue}
|
||||
>
|
||||
{navItemElement}
|
||||
</FeatureFlag>
|
||||
)
|
||||
}
|
||||
|
||||
return navItemElement
|
||||
})}
|
||||
</TreeNav>
|
||||
</OrgSettings>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -204,8 +213,18 @@ const mdtp: DispatchProps = {
|
|||
const mstp = (state: AppState): StateProps => {
|
||||
const isHidden = get(state, 'app.ephemeral.inPresentationMode', false)
|
||||
const navBarState = get(state, 'app.persisted.navBarState', 'collapsed')
|
||||
|
||||
return {isHidden, navBarState}
|
||||
const {settings} = get(state, 'cloud.orgSettings')
|
||||
let showUpgradeButton = false
|
||||
const hideUpgradeCTA = settings.find(
|
||||
(setting: OrgSetting) => setting.key === HIDE_UPGRADE_CTA_KEY
|
||||
)
|
||||
if (
|
||||
!hideUpgradeCTA ||
|
||||
hideUpgradeCTA.value !== PAID_ORG_HIDE_UPGRADE_SETTING.value
|
||||
) {
|
||||
showUpgradeButton = true
|
||||
}
|
||||
return {isHidden, navBarState, showUpgradeButton}
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps>(
|
||||
|
|
|
@ -1,25 +1,56 @@
|
|||
// Libraries
|
||||
import React, {FC} from 'react'
|
||||
import {Link} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
// Components
|
||||
import CloudOnly from 'src/shared/components/cloud/CloudOnly'
|
||||
|
||||
// Constants
|
||||
import {CLOUD_URL, CLOUD_CHECKOUT_PATH} from 'src/shared/constants'
|
||||
import {
|
||||
HIDE_UPGRADE_CTA_KEY,
|
||||
PAID_ORG_HIDE_UPGRADE_SETTING,
|
||||
} from 'src/cloud/constants'
|
||||
|
||||
const CloudUpgradeButton: FC = () => {
|
||||
// Types
|
||||
import {AppState, OrgSetting} from 'src/types'
|
||||
|
||||
interface StateProps {
|
||||
show: boolean
|
||||
}
|
||||
|
||||
const CloudUpgradeButton: FC<StateProps> = ({show}) => {
|
||||
return (
|
||||
<CloudOnly>
|
||||
<Link
|
||||
className="cf-button cf-button-sm cf-button-success upgrade-payg--button"
|
||||
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
|
||||
target="_self"
|
||||
>
|
||||
Upgrade Now
|
||||
</Link>
|
||||
{show ? (
|
||||
<Link
|
||||
className="cf-button cf-button-sm cf-button-success upgrade-payg--button"
|
||||
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
|
||||
target="_self"
|
||||
>
|
||||
Upgrade Now
|
||||
</Link>
|
||||
) : null}
|
||||
</CloudOnly>
|
||||
)
|
||||
}
|
||||
|
||||
export default CloudUpgradeButton
|
||||
const mstp = ({
|
||||
cloud: {
|
||||
orgSettings: {settings},
|
||||
},
|
||||
}: AppState) => {
|
||||
const hideUpgradeCTA = settings.find(
|
||||
(setting: OrgSetting) => setting.key === HIDE_UPGRADE_CTA_KEY
|
||||
)
|
||||
if (
|
||||
!hideUpgradeCTA ||
|
||||
hideUpgradeCTA.value !== PAID_ORG_HIDE_UPGRADE_SETTING.value
|
||||
) {
|
||||
return {show: true}
|
||||
}
|
||||
return {show: false}
|
||||
}
|
||||
|
||||
export default connect<StateProps>(mstp)(CloudUpgradeButton)
|
||||
|
|
|
@ -37,6 +37,10 @@ import {membersReducer} from 'src/members/reducers'
|
|||
import {autoRefreshReducer} from 'src/shared/reducers/autoRefresh'
|
||||
import {limitsReducer, LimitsState} from 'src/cloud/reducers/limits'
|
||||
import {demoDataReducer, DemoDataState} from 'src/cloud/reducers/demodata'
|
||||
import {
|
||||
orgSettingsReducer,
|
||||
OrgSettingsState,
|
||||
} from 'src/cloud/reducers/orgsettings'
|
||||
import checksReducer from 'src/checks/reducers'
|
||||
import rulesReducer from 'src/notifications/rules/reducers'
|
||||
import endpointsReducer from 'src/notifications/endpoints/reducers'
|
||||
|
@ -58,9 +62,14 @@ export const rootReducer = combineReducers<ReducerState>({
|
|||
...sharedReducers,
|
||||
autoRefresh: autoRefreshReducer,
|
||||
alertBuilder: alertBuilderReducer,
|
||||
cloud: combineReducers<{limits: LimitsState; demoData: DemoDataState}>({
|
||||
cloud: combineReducers<{
|
||||
limits: LimitsState
|
||||
demoData: DemoDataState
|
||||
orgSettings: OrgSettingsState
|
||||
}>({
|
||||
limits: limitsReducer,
|
||||
demoData: demoDataReducer,
|
||||
orgSettings: orgSettingsReducer,
|
||||
}),
|
||||
currentPage: currentPageReducer,
|
||||
currentDashboard: currentDashboardReducer,
|
||||
|
|
|
@ -39,3 +39,12 @@ export interface LimitsStatus {
|
|||
status: string
|
||||
}
|
||||
}
|
||||
export interface OrgSetting {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface OrgSettings {
|
||||
orgID: string
|
||||
settings: OrgSetting[]
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import {LimitsState} from 'src/cloud/reducers/limits'
|
|||
import {AlertBuilderState} from 'src/alerting/reducers/alertBuilder'
|
||||
import {CurrentPage} from 'src/shared/reducers/currentPage'
|
||||
import {DemoDataState} from 'src/cloud/reducers/demodata'
|
||||
import {OrgSettingsState} from 'src/cloud/reducers/orgsettings'
|
||||
|
||||
import {ResourceState} from 'src/types'
|
||||
|
||||
|
@ -32,7 +33,11 @@ export interface AppState {
|
|||
alertBuilder: AlertBuilderState
|
||||
app: AppPresentationState
|
||||
autoRefresh: AutoRefreshState
|
||||
cloud: {limits: LimitsState; demoData: DemoDataState}
|
||||
cloud: {
|
||||
limits: LimitsState
|
||||
demoData: DemoDataState
|
||||
orgSettings: OrgSettingsState
|
||||
}
|
||||
currentPage: CurrentPage
|
||||
currentDashboard: CurrentDashboardState
|
||||
dataLoading: DataLoadingState
|
||||
|
|
Loading…
Reference in New Issue