Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(demo): allow regular org members to create demo proj #13582

Merged
merged 5 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const FEATURE_FLAGS = {
WEBSITE_ANALYTICS_TEMPLATE: 'website-analytics-template', // owner: @pauldambra
VARIANT_OVERRIDES: 'variant-overrides', // owner: @neilkakkar
ONBOARDING_V2_EXPERIMENT: 'onboarding-v2-experiment', // owner: #team-growth
ONBOARDING_DEMO_EXPERIMENT: 'onboarding-demo-experiment', // owner: #team-growth
ONBOARDING_V2_DEMO: 'onboarding-v2-demo', // owner: #team-growth
FEATURE_FLAG_ROLLOUT_UX: 'feature-flag-rollout-ux', // owner: @neilkakkar
ROLE_BASED_ACCESS: 'role-based-access', // owner: #team-experiments, @liyiy
DASHBOARD_TEMPLATES: 'dashboard-templates', // owner @pauldambra
Expand Down
40 changes: 39 additions & 1 deletion frontend/src/scenes/ingestion/v2/ingestionLogicV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { BillingType, TeamType } from '~/types'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { inviteLogic } from 'scenes/organization/Settings/inviteLogic'
import type { ingestionLogicV2Type } from './ingestionLogicV2Type'
import api from 'lib/api'

export enum INGESTION_STEPS {
START = 'Get started',
Expand Down Expand Up @@ -168,7 +169,12 @@ export const ingestionLogicV2 = kea<ingestionLogicV2Type>([
inviteLogic,
['isInviteModalShown'],
],
actions: [teamLogic, ['updateCurrentTeamSuccess'], inviteLogic, ['inviteTeamMembersSuccess']],
actions: [
teamLogic,
['updateCurrentTeamSuccess', 'createTeamSuccess'],
inviteLogic,
['inviteTeamMembersSuccess'],
],
}),
actions({
setState: ({
Expand Down Expand Up @@ -201,6 +207,8 @@ export const ingestionLogicV2 = kea<ingestionLogicV2Type>([
onBack: true,
goToView: (view: INGESTION_VIEWS) => ({ view }),
setSidebarSteps: (steps: string[]) => ({ steps }),
setIsDemoDataReady: (isDemoDataReady: boolean) => ({ isDemoDataReady }),
setDemoDataInterval: (demoDataInterval: number) => ({ demoDataInterval }),
}),
windowValues({
isSmallScreen: (window: Window) => window.innerWidth < getBreakpoint('md'),
Expand Down Expand Up @@ -285,6 +293,18 @@ export const ingestionLogicV2 = kea<ingestionLogicV2Type>([
setState: (_, { generatingDemoData }) => generatingDemoData,
},
],
demoDataInterval: [
null as null | number,
{
setDemoDataInterval: (_, { demoDataInterval }) => demoDataInterval,
setIsDemoDataReady: (current, { isDemoDataReady }) => {
if (isDemoDataReady && current) {
clearInterval(current)
}
return null
},
},
],
}),
selectors(() => ({
currentState: [
Expand Down Expand Up @@ -514,6 +534,24 @@ export const ingestionLogicV2 = kea<ingestionLogicV2Type>([
actions.setState(viewToState(INGESTION_VIEWS.TEAM_INVITED, values.currentState as IngestionState))
}
},
createTeamSuccess: ({ currentTeam }) => {
if (window.location.href.includes(urls.ingestion()) && currentTeam.is_demo) {
const interval = window.setInterval(async () => {
const res = await api.get('api/projects/@current/is_generating_demo_data')
if (!res.is_generating_demo_data) {
actions.setIsDemoDataReady(true)
}
}, 1000)
actions.setDemoDataInterval(interval)
} else {
window.location.href = urls.ingestion()
}
},
setIsDemoDataReady: ({ isDemoDataReady }) => {
if (isDemoDataReady) {
window.location.href = urls.default()
}
},
})),
subscriptions(({ actions, values }) => ({
showBillingStep: (value) => {
Expand Down
15 changes: 5 additions & 10 deletions frontend/src/scenes/ingestion/v2/panels/InviteTeamPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useActions, useValues } from 'kea'
import { useActions } from 'kea'
import { ingestionLogicV2 } from 'scenes/ingestion/v2/ingestionLogicV2'
import { LemonButton } from 'lib/components/LemonButton'
import './Panels.scss'
Expand All @@ -7,14 +7,11 @@ import { IconChevronRight } from 'lib/components/icons'
import { inviteLogic } from 'scenes/organization/Settings/inviteLogic'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { DemoProjectButton } from './PanelComponents'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { FEATURE_FLAGS } from 'lib/constants'

export function InviteTeamPanel(): JSX.Element {
const { next } = useActions(ingestionLogicV2)
const { showInviteModal } = useActions(inviteLogic)
const { reportInviteMembersButtonClicked } = useActions(eventUsageLogic)
const { featureFlags } = useValues(featureFlagLogic)

return (
<div>
Expand Down Expand Up @@ -59,12 +56,10 @@ export function InviteTeamPanel(): JSX.Element {
</p>
</div>
</LemonButton>
{featureFlags[FEATURE_FLAGS.ONBOARDING_DEMO_EXPERIMENT] === 'test' ? (
<DemoProjectButton
text="I just want to try PostHog with some demo data."
subtext="Explore insights, create dashboards, try out cohorts, and more."
/>
) : null}
<DemoProjectButton
text="I just want to try PostHog with some demo data."
subtext="Explore insights, create dashboards, try out cohorts, and more."
/>
</div>
</div>
)
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/scenes/ingestion/v2/panels/PanelComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { teamLogic } from 'scenes/teamLogic'
import { organizationLogic } from 'scenes/organizationLogic'
import { userLogic } from 'scenes/userLogic'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'

const DEMO_TEAM_NAME: string = 'Hedgebox'

Expand Down Expand Up @@ -67,7 +69,11 @@ export function DemoProjectButton({ text, subtext }: { text: string; subtext?: s
const { currentOrganization } = useValues(organizationLogic)
const { updateCurrentTeam } = useActions(userLogic)
const { reportIngestionTryWithDemoDataClicked, reportProjectCreationSubmitted } = useActions(eventUsageLogic)
const { featureFlags } = useValues(featureFlagLogic)

if (featureFlags[FEATURE_FLAGS.ONBOARDING_V2_DEMO] !== 'test') {
return <></>
}
return (
<LemonButton
onClick={() => {
Expand Down
15 changes: 5 additions & 10 deletions frontend/src/scenes/ingestion/v2/panels/TeamInvitedPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import { useActions, useValues } from 'kea'
import { useActions } from 'kea'
import { ingestionLogicV2 } from 'scenes/ingestion/v2/ingestionLogicV2'
import { LemonButton } from 'lib/components/LemonButton'
import './Panels.scss'
import { LemonDivider } from 'lib/components/LemonDivider'
import { IconChevronRight } from 'lib/components/icons'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { DemoProjectButton } from './PanelComponents'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { FEATURE_FLAGS } from 'lib/constants'

export function TeamInvitedPanel(): JSX.Element {
const { completeOnboarding } = useActions(ingestionLogicV2)
const { reportIngestionContinueWithoutVerifying } = useActions(eventUsageLogic)
const { featureFlags } = useValues(featureFlagLogic)

return (
<div>
<h1 className="ingestion-title">Help is on the way!</h1>
<p className="prompt-text">You can still explore PostHog while you wait for your team members to join.</p>
<LemonDivider thick dashed className="my-6" />
<div className="flex flex-col mb-6">
{featureFlags[FEATURE_FLAGS.ONBOARDING_DEMO_EXPERIMENT] === 'test' ? (
<DemoProjectButton
text="Quickly try PostHog with some demo data."
subtext="Explore insights, create dashboards, try out cohorts, and more."
/>
) : null}
<DemoProjectButton
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be behind the new flag you created?

Copy link
Member Author

@raquelmsmith raquelmsmith Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put the check inside that component since it was implemented in a couple different places. https://github.com/PostHog/posthog/pull/13582/files#diff-b45ab9c675aae471e2211c373c0d3bbdb6faaf7f6db73d1a9e72594863f1744eR74

text="Quickly try PostHog with some demo data."
subtext="Explore insights, create dashboards, try out cohorts, and more."
/>
<LemonButton
onClick={() => {
completeOnboarding()
Expand Down
7 changes: 0 additions & 7 deletions frontend/src/scenes/teamLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,6 @@ export const teamLogic = kea<teamLogicType>([
deleteTeamSuccess: () => {
lemonToast.success('Project has been deleted')
},
createTeamSuccess: ({ currentTeam }) => {
if (window.location.href.includes('/ingestion') && currentTeam.is_demo) {
window.location.href = '/'
} else {
window.location.href = '/ingestion'
}
},
})),
events(({ actions }) => ({
afterMount: () => {
Expand Down
23 changes: 21 additions & 2 deletions posthog/api/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
CREATE_METHODS,
OrganizationAdminAnyPermissions,
OrganizationAdminWritePermissions,
OrganizationMemberPermissions,
ProjectMembershipNecessaryPermissions,
TeamMemberLightManagementPermission,
TeamMemberStrictManagementPermission,
Expand Down Expand Up @@ -153,7 +154,9 @@ def create(self, validated_data: Dict[str, Any], **kwargs) -> Team:
organization = self.context["view"].organization # Use the org we used to validate permissions
if validated_data.get("is_demo", False):
team = Team.objects.create(**validated_data, organization=organization)
create_data_for_demo_team.delay(team.pk, request.user.pk)
cache_key = f"is_generating_demo_data_{team.pk}"
cache.set(cache_key, "True") # create an item in the cache that we can use to see if the demo data is ready
create_data_for_demo_team.delay(team.pk, request.user.pk, cache_key)
else:
team = Team.objects.create_with_data(**validated_data, organization=organization)
request.user.current_team = team
Expand Down Expand Up @@ -219,7 +222,10 @@ def get_permissions(self) -> List:
raise exceptions.ValidationError("You need to belong to an organization.")
# To be used later by OrganizationAdminWritePermissions and TeamSerializer
self.organization = organization
base_permissions.append(OrganizationAdminWritePermissions())
if "is_demo" not in self.request.data or not self.request.data["is_demo"]:
base_permissions.append(OrganizationAdminWritePermissions())
elif "is_demo" in self.request.data:
base_permissions.append(OrganizationMemberPermissions())
elif self.action != "list":
# Skip TeamMemberAccessPermission for list action, as list is serialized with limited TeamBasicSerializer
base_permissions.append(TeamMemberLightManagementPermission())
Expand Down Expand Up @@ -279,3 +285,16 @@ def reset_token(self, request: request.Request, id: str, **kwargs) -> response.R
team.api_token = generate_random_token_project()
team.save()
return response.Response(TeamSerializer(team, context=self.get_serializer_context()).data)

@action(
methods=["GET"],
detail=True,
permission_classes=[
permissions.IsAuthenticated,
ProjectMembershipNecessaryPermissions,
],
)
def is_generating_demo_data(self, request: request.Request, id: str, **kwargs) -> response.Response:
team = self.get_object()
cache_key = f"is_generating_demo_data_{team.pk}"
return response.Response({"is_generating_demo_data": cache.get(cache_key) == "True"})
19 changes: 19 additions & 0 deletions posthog/api/test/test_team.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,25 @@ def test_update_timezone_remove_cache(self):
# Verify cache was deleted
self.assertEqual(cache.get(response["filters_hash"]), None)

def test_is_generating_demo_data(self):
cache_key = f"is_generating_demo_data_{self.team.pk}"
cache.set(cache_key, "True")
response = self.client.get(f"/api/projects/{self.team.id}/is_generating_demo_data/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), {"is_generating_demo_data": True})
cache.delete(cache_key)
response = self.client.get(f"/api/projects/{self.team.id}/is_generating_demo_data/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), {"is_generating_demo_data": False})

@patch("posthog.api.team.create_data_for_demo_team.delay")
def test_org_member_can_create_demo_project(self, mock_create_data_for_demo_team: MagicMock):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
response = self.client.post("/api/projects/", {"name": "Hedgebox", "is_demo": True})
mock_create_data_for_demo_team.assert_called_once()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)


def create_team(organization: Organization, name: str = "Test team") -> Team:
"""
Expand Down
11 changes: 4 additions & 7 deletions posthog/tasks/demo_create_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from celery import shared_task
from sentry_sdk import capture_exception
from django.core.cache import cache

from posthog.demo.matrix import manager
from posthog.demo.products.hedgebox.matrix import HedgeboxMatrix
Expand All @@ -8,12 +8,9 @@


@shared_task(ignore_result=True)
def create_data_for_demo_team(team_id: int, user_id: int) -> None:
def create_data_for_demo_team(team_id: int, user_id: int, cache_key: str) -> None:
team = Team.objects.get(pk=team_id)
user = User.objects.get(pk=user_id)
if team and user:
try:
manager.MatrixManager(HedgeboxMatrix(), use_pre_save=True).run_on_team(team, user)
except Exception as e: # TODO: Remove this after 2022-12-22, the except is just temporary for debugging
capture_exception(e)
raise e
manager.MatrixManager(HedgeboxMatrix(), use_pre_save=True).run_on_team(team, user)
cache.delete(cache_key)