0
0
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:
Michael Matloka 2021-09-11 00:28:23 +02:00 committed by GitHub
parent 08efd78987
commit 1d07c7670d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 36 additions and 48 deletions

View File

@ -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.',

View File

@ -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>

View File

@ -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
}

View File

@ -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) {

View File

@ -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()