Skip to content

Commit

Permalink
Setup improvements - Preflight (#1473)
Browse files Browse the repository at this point in the history
Co-authored-by: Twixes <dev@twixes.com>
  • Loading branch information
paolodamico and Twixes committed Aug 20, 2020
1 parent 12eeaf9 commit 7605d7a
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 22 deletions.
Binary file added frontend/public/hedgehog-blue.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion frontend/src/scenes/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const OnboardingWizard = lazy(() => import('~/scenes/onboarding/onboardingWizard
import BillingToolbar from 'lib/components/BillingToolbar'

import { userLogic } from 'scenes/userLogic'
import { sceneLogic } from 'scenes/sceneLogic'
import { sceneLogic, unauthenticatedRoutes } from 'scenes/sceneLogic'
import { SceneLoading } from 'lib/utils'
import { router } from 'kea-router'

Expand Down Expand Up @@ -51,6 +51,10 @@ function App() {
setImage(urlBackgroundMap[location.pathname])
}, [location.pathname])

if (unauthenticatedRoutes.includes(scene)) {
return <Scene {...params} />
}

if (!user) {
return null
}
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/scenes/sceneLogic.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ export const scenes = {
annotations: () => import(/* webpackChunkName: 'annotations' */ './annotations/AnnotationsScene'),
team: () => import(/* webpackChunkName: 'team' */ './team/Team'),
licenses: () => import(/* webpackChunkName: 'setup' */ './setup/Licenses'),
preflight: () => import(/* webpackChunkName: 'preflightCheck' */ './setup/PreflightCheck'),
}

/* List of routes that do not require authentication (N.B. add to posthog.urls too) */
export const unauthenticatedRoutes = ['preflight']

export const redirects = {
'/': '/insights',
}
Expand All @@ -51,6 +55,7 @@ export const routes = {
'/annotations': 'annotations',
'/team': 'team',
'/setup/licenses': 'licenses',
'/preflight': 'preflight',
}

export const sceneLogic = kea({
Expand Down
253 changes: 253 additions & 0 deletions frontend/src/scenes/setup/PreflightCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import React, { useState, useEffect } from 'react'
import { useValues, useActions } from 'kea'
import { preflightLogic } from './preflightCheckLogic'
import { Row, Col, Space, Card, Button } from 'antd'
import hedgehogBlue from './../../../public/hedgehog-blue.jpg'
import {
CheckSquareFilled,
CloseSquareFilled,
LoadingOutlined,
SyncOutlined,
WarningFilled,
RocketFilled,
ApiTwoTone,
} from '@ant-design/icons'
import { volcano, green, red, grey, blue } from '@ant-design/colors'

function PreflightItem(props) {
/*
status === undefined -> Item still loading (no positive or negative response yet)
status === false -> Item not ready (fail to validate)
status === true -> Item ready (validated)
*/
const { name, status, caption, failedState } = props
let textColor

if (status) textColor = green.primary
else if (status === false) {
if (failedState === 'warning') textColor = volcano.primary
else if (failedState === 'not-required') textColor = grey.primary
else textColor = red.primary
} else textColor = grey.primary

return (
<Col span={12} style={{ textAlign: 'left', marginBottom: 16, display: 'flex', alignItems: 'center' }}>
{status === false && failedState !== 'warning' && (
<CloseSquareFilled style={{ fontSize: 20, color: textColor }} />
)}
{status === false && failedState === 'warning' && (
<WarningFilled style={{ fontSize: 20, color: textColor }} />
)}

{status === true && <CheckSquareFilled style={{ fontSize: 20, color: textColor }} />}
{status !== true && status !== false && <LoadingOutlined style={{ fontSize: 20, color: textColor }} />}
<span style={{ color: textColor, paddingLeft: 8 }}>
{name} {caption && status === false && <div style={{ fontSize: 12 }}>{caption}</div>}
</span>
</Col>
)
}

function PreflightCheck() {
const [state, setState] = useState({})
const logic = preflightLogic()
const { preflight } = useValues(logic)
const { loadPreflight, loadPreflightSuccess } = useActions(logic)
const isReady = preflight.django && preflight.db && preflight.redis

const checks = [
{
id: 'database',
name: 'Database (Postgres)',
status: preflight.db,
},
{
id: 'backend',
name: 'Backend server (Django)',
status: preflight.django,
},
{
id: 'redis',
name: 'Queue processing (Redis)',
status: preflight.redis,
},
{
id: 'frontend',
name: 'Frontend built (Webpack)',
status: true, // If this code is run, the front-end is already built
},
{
id: 'tls',
name: 'SSL/TLS certificate',
status: window.location.protocol === 'https:',
caption:
state.mode === 'Experimentation'
? 'Not required for development or testing'
: 'Install before ingesting real user data',
failedState: state.mode === 'Experimentation' ? 'not-required' : 'warning',
},
]

const runChecks = () => {
// Clear the previous result first and add the timeout to show the loading animation
loadPreflightSuccess({})
setTimeout(() => loadPreflight(), 1000)
}

const handleModeChange = (mode) => {
setState({ ...state, mode })
if (mode) {
runChecks()
localStorage.setItem('preflightMode', mode)
} else {
localStorage.removeItem('preflightMode')
}
}

const handlePreflightFinished = () => {
window.location.href = '/setup_admin'
}

useEffect(() => {
const mode = localStorage.getItem('preflightMode')
if (mode) handleModeChange(mode)
}, [])

return (
<>
<Space direction="vertical" className="space-top" style={{ width: '100%' }}>
<h1 className="title text-center" style={{ marginBottom: 0 }}>
Welcome to PostHog!
</h1>
<div className="page-caption text-center">Understand your users. Build a better product.</div>
</Space>
<Col xs={24} style={{ margin: '32px 16px' }}>
<h2 className="subtitle text-center space-top">
We're&nbsp;glad to&nbsp;have you&nbsp;here! Let's&nbsp;get&nbsp;you started with&nbsp;PostHog.
</h2>
</Col>
<Row style={{ display: 'flex', justifyContent: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', flexDirection: 'column' }}>
<img src={hedgehogBlue} style={{ maxHeight: '100%' }} />
<p>Got any PostHog questions?</p>
<Button type="default" data-attr="support" data-source="preflight">
<a href="https://posthog.com/support" target="_blank" rel="noreferrer">
Find support
</a>
</Button>
<div className="breadcrumbs space-top">
<span className="selected">Preflight check</span> &gt; Event capture &gt; Team setup
</div>
</div>
<div
style={{
display: 'flex',
justifyContent: 'flex-start',
margin: '0 32px',
flexDirection: 'column',
paddingTop: 32,
}}
>
<Card style={{ width: 600, width: '100%' }}>
<Row style={{ display: 'flex', justifyContent: 'space-between', lineHeight: '32px' }}>
{!state.mode && <b style={{ fontSize: 16 }}>Select preflight mode</b>}
{state.mode && (
<>
<b style={{ fontSize: 16 }}>
<span>
<span
style={{ color: blue.primary, cursor: 'pointer' }}
onClick={() => handleModeChange(null)}
>
Select preflight mode
</span>{' '}
&gt; {state.mode}
</span>
</b>
<Button
type="default"
data-attr="preflight-refresh"
icon={<SyncOutlined />}
onClick={() => window.location.reload()}
disabled={Object.keys(preflight).length === 0}
>
Refresh
</Button>
</>
)}
</Row>
{!state.mode && (
<div>
What's your plan for this installation? We'll make infrastructure checks accordingly.
</div>
)}
<div
className="text-center"
style={{ padding: '24px 0', display: 'flex', justifyContent: 'center', maxWidth: 533 }}
>
{!state.mode && (
<>
<Button
type="default"
data-attr="preflight-experimentation"
onClick={() => handleModeChange('Experimentation')}
icon={<ApiTwoTone />}
>
Just experimenting
</Button>
<Button
type="primary"
style={{ marginLeft: 16 }}
data-attr="preflight-live"
onClick={() => handleModeChange('Live')}
icon={<RocketFilled />}
>
Live implementation
</Button>
</>
)}

{state.mode && (
<>
<Row>
{checks.map((item) => (
<PreflightItem key={item.id} {...item} />
))}
</Row>
</>
)}
</div>

<div style={{ fontSize: 12, textAlign: 'center' }}>
We will not enforce some security requirements in experimentation mode.
</div>
</Card>
{state.mode && (
<>
<div className="space-top text-center">
{isReady ? (
<b style={{ color: green.primary }}>All systems go!</b>
) : (
<b>Checks in progress…</b>
)}
</div>
<div className="space-top text-center" style={{ marginBottom: 64 }}>
<Button
type="primary"
data-attr="preflight-complete"
data-source={state.mode}
disabled={!isReady}
onClick={handlePreflightFinished}
>
Continue
</Button>
</div>
</>
)}
</div>
</Row>
</>
)
}

export default PreflightCheck
15 changes: 15 additions & 0 deletions frontend/src/scenes/setup/preflightCheckLogic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { kea } from 'kea'
import api from 'lib/api'

export const preflightLogic = kea({
loaders: () => ({
preflight: [
[],
{
loadPreflight: async () => {
return await api.get('_preflight/')
},
},
],
}),
})
27 changes: 27 additions & 0 deletions frontend/src/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,22 @@ h1.page-header {
font-size: 24px;
}

h1.title {
// New implementation of h1.page-hader
font-size: 32px;
font-weight: bold;
}

h2.subtitle {
font-size: 18px;
font-weight: bold;
}

.page-caption {
color: rgba(0, 0, 0, 0.6);
font-size: 14px;
}

button.no-style {
background: none;
border: none;
Expand Down Expand Up @@ -374,3 +390,14 @@ label.disabled {
}
}
}

.space-top {
margin-top: 32px;
}

.breadcrumbs {
.selected {
color: #35416b;
font-weight: bold;
}
}
22 changes: 22 additions & 0 deletions posthog/api/test/test_preflight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from rest_framework import status

from .base import BaseTest


class TestPreflight(BaseTest):
def test_preflight_request(self):
self.client.logout() # make sure it works anonymously

response = self.client.get("/_preflight/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = response.json()
self.assertEqual(response["django"], True)
self.assertEqual(response["db"], True)

def test_preflight_request_no_redis(self):

with self.settings(REDIS_URL=None):
response = self.client.get("/_preflight/")

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), {"django": True, "redis": False, "db": True})
4 changes: 2 additions & 2 deletions posthog/api/test/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def test_slack_webhook_bad_url_full(self):


class TestLoginViews(BaseTest):
def test_redirect_to_setup_admin_when_no_users(self):
def test_redirect_to_preflight_when_no_users(self):
User.objects.all().delete()
response = self.client.get("/", follow=True)
self.assertRedirects(response, "/setup_admin")
self.assertRedirects(response, "/preflight")
Loading

0 comments on commit 7605d7a

Please sign in to comment.