mirror of
https://github.com/PostHog/posthog.git
synced 2024-12-01 12:21:02 +01:00
Improve premium feature guards (#5890)
* Kea-ify `guardAvailableFeature` * Fix `RestrictedArea` tooltip placement * Don't needlessly fetch all org fields in available features sync Fetching all fields is especially painful when there's a field-adding migration that hasn't ran yet, as Django selects fields explicitly, and when it does that with a field it knows about but the DB doesn't yet, the whole query fails. And because `sync_all_organization_available_features` is ran on Django server start, the server fails to start, refusing to hot reload again until a full server restart.
This commit is contained in:
parent
08efd78987
commit
1d07c7670d
@ -22,7 +22,6 @@ import {
|
||||
KeyOutlined,
|
||||
SmileOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { guardAvailableFeature } from 'scenes/UpgradeModal'
|
||||
import { sceneLogic, urls } from 'scenes/sceneLogic'
|
||||
import { CreateProjectModal } from 'scenes/project/CreateProjectModal'
|
||||
import { CreateOrganizationModal } from 'scenes/organization/CreateOrganizationModal'
|
||||
@ -75,7 +74,7 @@ export function TopNavigation(): JSX.Element {
|
||||
const { preflight } = useValues(preflightLogic)
|
||||
const { billing } = useValues(billingLogic)
|
||||
const { logout, updateCurrentTeam, updateCurrentOrganization } = useActions(userLogic)
|
||||
const { showUpgradeModal } = useActions(sceneLogic)
|
||||
const { guardAvailableFeature } = useActions(sceneLogic)
|
||||
const { sceneConfig } = useValues(sceneLogic)
|
||||
const { push } = router.actions
|
||||
const { showPalette } = useActions(commandPaletteLogic)
|
||||
@ -174,9 +173,6 @@ export function TopNavigation(): JSX.Element {
|
||||
className="plain-button"
|
||||
onClick={() =>
|
||||
guardAvailableFeature(
|
||||
user,
|
||||
preflight,
|
||||
showUpgradeModal,
|
||||
AvailableFeature.ORGANIZATIONS_PROJECTS,
|
||||
'multiple organizations',
|
||||
'Organizations group people building products together. An organization can then have multiple projects.',
|
||||
@ -247,9 +243,6 @@ export function TopNavigation(): JSX.Element {
|
||||
className="plain-button"
|
||||
onClick={() =>
|
||||
guardAvailableFeature(
|
||||
user,
|
||||
preflight,
|
||||
showUpgradeModal,
|
||||
AvailableFeature.ORGANIZATIONS_PROJECTS,
|
||||
'multiple projects',
|
||||
'Projects allow you to separate data and configuration for different products or environments.',
|
||||
|
@ -36,7 +36,7 @@ export function RestrictedArea({ Component, minimumAccessLevel }: RestrictedArea
|
||||
}, [currentOrganization])
|
||||
|
||||
return restrictionReason ? (
|
||||
<Tooltip title={restrictionReason}>
|
||||
<Tooltip title={restrictionReason} placement="topLeft">
|
||||
<span>
|
||||
<Component isRestricted={true} restrictionReason={restrictionReason} />
|
||||
</span>
|
||||
|
@ -2,7 +2,6 @@ import Modal from 'antd/lib/modal/Modal'
|
||||
import { useActions, useValues } from 'kea'
|
||||
import { capitalizeFirstLetter } from 'lib/utils'
|
||||
import React from 'react'
|
||||
import { AvailableFeature, PreflightStatus, UserType } from '~/types'
|
||||
import { sceneLogic } from './sceneLogic'
|
||||
|
||||
export function UpgradeModal(): JSX.Element {
|
||||
@ -28,39 +27,3 @@ export function UpgradeModal(): JSX.Element {
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export function guardAvailableFeature(
|
||||
user: UserType | null,
|
||||
preflight: PreflightStatus | null,
|
||||
showUpgradeModal: (featureName: string, featureCaption: string) => void,
|
||||
key: AvailableFeature,
|
||||
name: string,
|
||||
caption: string,
|
||||
featureAvailableCallback?: () => void,
|
||||
guardOn: {
|
||||
cloud: boolean
|
||||
selfHosted: boolean
|
||||
} = {
|
||||
cloud: true,
|
||||
selfHosted: true,
|
||||
}
|
||||
): boolean {
|
||||
let featureAvailable: boolean
|
||||
if (!preflight || !user) {
|
||||
featureAvailable = false
|
||||
} else if (!guardOn.cloud && preflight.cloud) {
|
||||
featureAvailable = true
|
||||
} else if (!guardOn.selfHosted && !preflight.cloud) {
|
||||
featureAvailable = true
|
||||
} else {
|
||||
featureAvailable = !!user.organization?.available_features.includes(key)
|
||||
}
|
||||
|
||||
if (featureAvailable) {
|
||||
featureAvailableCallback?.()
|
||||
} else {
|
||||
showUpgradeModal(name, caption)
|
||||
}
|
||||
|
||||
return !featureAvailable
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
|
||||
import { preflightLogic } from './PreflightCheck/logic'
|
||||
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
|
||||
import { FEATURE_FLAGS } from 'lib/constants'
|
||||
import { ViewType } from '~/types'
|
||||
import { AvailableFeature, ViewType } from '~/types'
|
||||
import { userLogic } from './userLogic'
|
||||
import { afterLoginRedirect } from './authentication/loginLogic'
|
||||
|
||||
@ -262,6 +262,19 @@ export const sceneLogic = kea<sceneLogicType<LoadedScene, Params, Scene, SceneCo
|
||||
setScene: (scene: Scene, params: Params) => ({ scene, params }),
|
||||
setLoadedScene: (scene: Scene, loadedScene: LoadedScene) => ({ scene, loadedScene }),
|
||||
showUpgradeModal: (featureName: string, featureCaption: string) => ({ featureName, featureCaption }),
|
||||
guardAvailableFeature: (
|
||||
featureKey: AvailableFeature,
|
||||
featureName: string,
|
||||
featureCaption: string,
|
||||
featureAvailableCallback?: () => void,
|
||||
guardOn: {
|
||||
cloud: boolean
|
||||
selfHosted: boolean
|
||||
} = {
|
||||
cloud: true,
|
||||
selfHosted: true,
|
||||
}
|
||||
) => ({ featureKey, featureName, featureCaption, featureAvailableCallback, guardOn }),
|
||||
hideUpgradeModal: true,
|
||||
takeToPricing: true,
|
||||
setPageTitle: (title: string) => ({ title }),
|
||||
@ -333,6 +346,25 @@ export const sceneLogic = kea<sceneLogicType<LoadedScene, Params, Scene, SceneCo
|
||||
showUpgradeModal: ({ featureName }) => {
|
||||
eventUsageLogic.actions.reportUpgradeModalShown(featureName)
|
||||
},
|
||||
guardAvailableFeature: ({ featureKey, featureName, featureCaption, featureAvailableCallback, guardOn }) => {
|
||||
const { preflight } = preflightLogic.values
|
||||
const { user } = userLogic.values
|
||||
let featureAvailable: boolean
|
||||
if (!preflight || !user) {
|
||||
featureAvailable = false
|
||||
} else if (!guardOn.cloud && preflight.cloud) {
|
||||
featureAvailable = true
|
||||
} else if (!guardOn.selfHosted && !preflight.cloud) {
|
||||
featureAvailable = true
|
||||
} else {
|
||||
featureAvailable = !!user.organization?.available_features.includes(featureKey)
|
||||
}
|
||||
if (featureAvailable) {
|
||||
featureAvailableCallback?.()
|
||||
} else {
|
||||
actions.showUpgradeModal(featureName, featureCaption)
|
||||
}
|
||||
},
|
||||
takeToPricing: () => {
|
||||
posthog.capture('upgrade modal pricing interaction')
|
||||
if (preflightLogic.values.preflight?.cloud) {
|
||||
|
@ -4,6 +4,6 @@ from posthog.models.organization import Organization
|
||||
|
||||
|
||||
def sync_all_organization_available_features() -> None:
|
||||
for organization in cast(Sequence[Organization], Organization.objects.all()):
|
||||
for organization in cast(Sequence[Organization], Organization.objects.all().only("id")):
|
||||
organization.update_available_features()
|
||||
organization.save()
|
||||
|
Loading…
Reference in New Issue
Block a user