Skip to content

Commit

Permalink
Merge branch 'master' into tom/trends-cohort-breakdowns
Browse files Browse the repository at this point in the history
  • Loading branch information
Gilbert09 authored Jan 16, 2024
2 parents dc2bb1b + 242e8e9 commit 5b74088
Show file tree
Hide file tree
Showing 74 changed files with 1,302 additions and 586 deletions.
2 changes: 1 addition & 1 deletion cypress/e2e/systemStatus.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { urls } from 'scenes/urls'

describe('System Status', () => {
it('System Status loaded', () => {
cy.location('pathname').should('eq', urls.savedInsights())
cy.location('pathname').should('eq', '/project/1/insights')
cy.wait(500)
cy.get('[data-attr=menu-item-me]').click()
cy.get('[data-attr=system-status-badge]').click()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { SidePanelActivation, SidePanelActivationIcon } from './panels/SidePanel
import { SidePanelDocs } from './panels/SidePanelDocs'
import { SidePanelFeaturePreviews } from './panels/SidePanelFeaturePreviews'
import { SidePanelSettings } from './panels/SidePanelSettings'
import { SidePanelStatus, SidePanelStatusIcon } from './panels/SidePanelStatus'
import { SidePanelSupport } from './panels/SidePanelSupport'
import { SidePanelWelcome } from './panels/SidePanelWelcome'
import { sidePanelLogic } from './sidePanelLogic'
Expand Down Expand Up @@ -71,6 +72,11 @@ export const SIDE_PANEL_TABS: Record<SidePanelTab, { label: string; Icon: any; C
Icon: IconConfetti,
Content: SidePanelWelcome,
},
[SidePanelTab.Status]: {
label: 'System status',
Icon: SidePanelStatusIcon,
Content: SidePanelStatus,
},
}

const DEFAULT_WIDTH = 512
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Menu = {
url?: string
}

function SidePanelDocsSkeleton(): JSX.Element {
export function SidePanelDocsSkeleton(): JSX.Element {
return (
<div className="absolute inset-0 p-4 space-y-2">
<LemonSkeleton className="w-full h-10 mb-12" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { IconCloud, IconExternal } from '@posthog/icons'
import { LemonButton, Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { IconWithBadge } from 'lib/lemon-ui/icons'
import { useState } from 'react'

import { SidePanelPaneHeader } from '../components/SidePanelPaneHeader'
import { sidePanelLogic } from '../sidePanelLogic'
import { SidePanelDocsSkeleton } from './SidePanelDocs'
import { sidePanelStatusLogic, STATUS_PAGE_BASE } from './sidePanelStatusLogic'

export const SidePanelStatusIcon = (props: { className?: string }): JSX.Element => {
const { status, statusPage } = useValues(sidePanelStatusLogic)

let title = statusPage?.status.description
if (statusPage?.status.description === 'All Systems Operational') {
title = 'All systems operational' // Sentence-case Statuspage.io default message, which can't be changed
}

return (
<Tooltip title={title} placement="left">
<span {...props}>
<IconWithBadge
content={status !== 'operational' ? '!' : '✓'}
status={status.includes('outage') ? 'danger' : status.includes('degraded') ? 'warning' : 'success'}
>
<IconCloud />
</IconWithBadge>
</span>
</Tooltip>
)
}

export const SidePanelStatus = (): JSX.Element => {
const { closeSidePanel } = useActions(sidePanelLogic)
const [ready, setReady] = useState(false)

return (
<>
<SidePanelPaneHeader>
<div className="flex-1" />
<LemonButton
size="small"
sideIcon={<IconExternal />}
targetBlank
// We can't use the normal `to` property as that is intercepted to open this panel :D
onClick={() => {
window.open(STATUS_PAGE_BASE, '_blank')?.focus()
closeSidePanel()
}}
>
Open in new tab
</LemonButton>
</SidePanelPaneHeader>
<div className="relative flex-1 overflow-hidden">
<iframe
src={STATUS_PAGE_BASE}
title="Status"
className={clsx('w-full h-full', !ready && 'hidden')}
onLoad={() => setReady(true)}
/>

{!ready && <SidePanelDocsSkeleton />}
</div>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { actions, afterMount, beforeUnmount, connect, kea, listeners, path, reducers } from 'kea'
import { loaders } from 'kea-loaders'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'

import { sidePanelStateLogic } from '../sidePanelStateLogic'
import type { sidePanelStatusLogicType } from './sidePanelStatusLogicType'

export type SPIndicator = 'none' | 'minor' | 'major'

export type SPComponentStatus = 'operational' | 'degraded_performance' | 'partial_outage' | 'major_outage'

export interface SPSummary {
// page: SPPage
components: SPComponent[]
incidents: SPIncident[]
scheduled_maintenances: any[]
status: SPStatus
}

export interface SPComponent {
id: string
name: string
status: SPComponentStatus
created_at: Date
updated_at: Date
position: number
description: null | string
showcase: boolean
start_date: Date | null
group_id: null | string
page_id: string
group: boolean
only_show_if_degraded: boolean
components?: string[]
}

export interface SPIncident {
id: string
name: string
status: string
created_at: Date
updated_at: Date
monitoring_at: null
resolved_at: null
impact: string
shortlink: string
started_at: Date
page_id: string
incident_updates: SPIncidentUpdate[]
components: SPComponent[]
reminder_intervals: string
}

export interface SPIncidentUpdate {
id: string
status: string
body: string
incident_id: string
created_at: Date
updated_at: Date
display_at: Date
affected_components: SPAffectedComponent[]
deliver_notifications: boolean
custom_tweet: null
tweet_id: null
}

export interface SPAffectedComponent {
code: string
name: string
old_status: string
new_status: string
}

export interface SPStatus {
indicator: SPIndicator
description: string
}

export const STATUS_PAGE_BASE = 'https://status.posthog.com'

// NOTE: Test account with some incidents - ask @benjackwhite for access
// export const STATUS_PAGE_BASE = 'https://posthogtesting.statuspage.io'

// Map the hostname to relevant groups (found via the summary.json endpoint)
const RELEVANT_GROUPS_MAP = {
'us.posthog.com': ['41df083ftqt6', 'z0y6m9kyvy3j'],
'eu.posthog.com': ['c4d9jd1jcx3f', 'nfknrn2bf3yz'],
localhost: ['f58xx1143yvt', 't3rdjq2z0x7p'], // localhost has IDs for the test status page - that way we really only show it if local dev and overridden to use the other status page
}

export const REFRESH_INTERVAL = 60 * 1000 * 5 // 5 minutes

export const sidePanelStatusLogic = kea<sidePanelStatusLogicType>([
path(['scenes', 'navigation', 'sidepanel', 'sidePanelStatusLogic']),
connect({
values: [featureFlagLogic, ['featureFlags']],
actions: [sidePanelStateLogic, ['openSidePanel', 'closeSidePanel']],
}),

actions({
loadStatusPage: true,
}),

reducers(() => ({
// Persisted copy to avoid flash effect on page load
status: [
'operational' as SPComponentStatus,
{ persist: true },
{
loadStatusPageSuccess: (_, { statusPage }) => {
const relevantGroups = RELEVANT_GROUPS_MAP[window.location.hostname]
if (!relevantGroups) {
return 'operational'
}

return (
statusPage.components.find(
({ group_id, status }) => relevantGroups.includes(group_id) && status !== 'operational'
)?.status || 'operational'
)
},
loadStatusPageFailure: () => 'operational',
},
],
})),

loaders(() => ({
statusPage: [
null as SPSummary | null,
{
loadStatusPage: async () => {
const response = await fetch(`${STATUS_PAGE_BASE}/api/v2/summary.json`)
const data: SPSummary = await response.json()

return data
},
},
],
})),

listeners(({ actions, cache }) => ({
loadStatusPageSuccess: () => {
clearTimeout(cache.timeout)
cache.timeout = setTimeout(() => actions.loadStatusPage(), REFRESH_INTERVAL)
},
})),

afterMount(({ actions, values }) => {
if (values.featureFlags[FEATURE_FLAGS.SIDEPANEL_STATUS]) {
actions.loadStatusPage()
}
}),

beforeUnmount(({ cache }) => {
clearTimeout(cache.timeout)
}),
])
18 changes: 15 additions & 3 deletions frontend/src/layout/navigation-3000/sidepanel/sidePanelLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SidePanelTab } from '~/types'

import { sidePanelActivityLogic } from './panels/activity/sidePanelActivityLogic'
import { sidePanelDiscussionLogic } from './panels/discussion/sidePanelDiscussionLogic'
import { sidePanelStatusLogic } from './panels/sidePanelStatusLogic'
import type { sidePanelLogicType } from './sidePanelLogicType'
import { sidePanelStateLogic } from './sidePanelStateLogic'

Expand All @@ -18,6 +19,7 @@ const ALWAYS_EXTRA_TABS = [
SidePanelTab.FeaturePreviews,
SidePanelTab.Activity,
SidePanelTab.Welcome,
SidePanelTab.Status,
]

export const sidePanelLogic = kea<sidePanelLogicType>([
Expand All @@ -37,6 +39,8 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
['unreadCount'],
sidePanelDiscussionLogic,
['commentCount', 'commentCountLoading'],
sidePanelStatusLogic,
['status'],
],
actions: [sidePanelStateLogic, ['closeSidePanel', 'openSidePanel']],
}),
Expand Down Expand Up @@ -85,7 +89,6 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
if (isCloudOrDev) {
tabs.push(SidePanelTab.Support)
}
tabs.push(SidePanelTab.Settings)
tabs.push(SidePanelTab.Activity)
if (isReady && !hasCompletedAllTasks) {
tabs.push(SidePanelTab.Activation)
Expand All @@ -94,15 +97,20 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
tabs.push(SidePanelTab.Discussion)
}
tabs.push(SidePanelTab.FeaturePreviews)
tabs.push(SidePanelTab.Settings)
tabs.push(SidePanelTab.Welcome)

if (isCloudOrDev && featureflags[FEATURE_FLAGS.SIDEPANEL_STATUS]) {
tabs.push(SidePanelTab.Status)
}

return tabs
},
],

visibleTabs: [
(s) => [s.enabledTabs, s.selectedTab, s.sidePanelOpen, s.commentCount, s.unreadCount],
(enabledTabs, selectedTab, sidePanelOpen, commentCount, unreadCount): SidePanelTab[] => {
(s) => [s.enabledTabs, s.selectedTab, s.sidePanelOpen, s.commentCount, s.unreadCount, s.status],
(enabledTabs, selectedTab, sidePanelOpen, commentCount, unreadCount, status): SidePanelTab[] => {
return enabledTabs.filter((tab) => {
if (tab === selectedTab && sidePanelOpen) {
return true
Expand All @@ -116,6 +124,10 @@ export const sidePanelLogic = kea<sidePanelLogicType>([
return true
}

if (tab === SidePanelTab.Status && status !== 'operational') {
return true
}

// Hide certain tabs unless they are selected
if (ALWAYS_EXTRA_TABS.includes(tab)) {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export function SmoothingFilter(): JSX.Element | null {
return null
}

const { smoothing_intervals } = trendsFilter || {}
const { smoothingIntervals } = trendsFilter || {}

// Put a little icon next to the selected item
const options = smoothingOptions[interval].map(({ value, label }) => ({
value,
label:
value === smoothing_intervals ? (
value === smoothingIntervals ? (
<>
<FundOutlined className="mr-1 text-muted" /> {label}
</>
Expand All @@ -36,11 +36,11 @@ export function SmoothingFilter(): JSX.Element | null {
return options.length ? (
<LemonSelect
key={interval}
value={smoothing_intervals || 1}
value={smoothingIntervals || 1}
dropdownMatchSelectWidth={false}
onChange={(key) => {
updateInsightFilter({
smoothing_intervals: key,
smoothingIntervals: key,
})
}}
data-attr="smoothing-filter"
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/components/UnitPicker/CustomUnitModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ function chooseFormativeElementValue(
trendsFilter: TrendsFilter | null | undefined
): string {
if (formativeElement === 'prefix') {
return trendsFilter?.aggregation_axis_prefix || ''
return trendsFilter?.aggregationAxisPrefix || ''
}

if (formativeElement === 'postfix') {
return trendsFilter?.aggregation_axis_postfix || ''
return trendsFilter?.aggregationAxisPostfix || ''
}

return ''
Expand Down
Loading

0 comments on commit 5b74088

Please sign in to comment.