Skip to content

Commit

Permalink
Merge branch 'master' into fix/type-mismatch-error-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldambra authored Sep 23, 2024
2 parents bc81157 + 54b0134 commit 54d798b
Show file tree
Hide file tree
Showing 24 changed files with 181 additions and 44 deletions.
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.
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.
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.
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.
2 changes: 2 additions & 0 deletions frontend/src/lib/api.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const MOCK_DEFAULT_ORGANIZATION_MEMBER: OrganizationMemberType = {
updated_at: '2020-09-24T15:05:26.758837Z',
is_2fa_enabled: false,
has_social_auth: false,
last_login: '2020-09-24T15:05:26.758796Z',
}

export const MOCK_SECOND_BASIC_USER: UserBasicType = {
Expand All @@ -181,6 +182,7 @@ export const MOCK_SECOND_ORGANIZATION_MEMBER: OrganizationMemberType = {
updated_at: '2021-03-11T19:11:11Z',
is_2fa_enabled: false,
has_social_auth: false,
last_login: '2020-09-24T15:05:26.758796Z',
}

export const MOCK_DEFAULT_ORGANIZATION_INVITE: OrganizationInviteType = {
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/lib/lemon-ui/LemonSwitch/LemonSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface LemonSwitchProps {
tooltip?: string | JSX.Element | null
handleContent?: React.ReactElement | null
'aria-label'?: string
sliderColorOverrideChecked?: string
sliderColorOverrideUnchecked?: string
}

/** Counter used for collision-less automatic switch IDs. */
Expand All @@ -44,6 +46,8 @@ export const LemonSwitch: React.FunctionComponent<LemonSwitchProps & React.RefAt
'data-attr': dataAttr,
'aria-label': ariaLabel,
handleContent,
sliderColorOverrideChecked,
sliderColorOverrideUnchecked,
},
ref
): JSX.Element {
Expand Down Expand Up @@ -80,7 +84,13 @@ export const LemonSwitch: React.FunctionComponent<LemonSwitchProps & React.RefAt
disabled={disabled}
{...conditionalProps}
>
<div className="LemonSwitch__slider" />
<div
className={`LemonSwitch__slider ${
sliderColorOverrideChecked || sliderColorOverrideUnchecked
? `bg-${checked ? sliderColorOverrideChecked : sliderColorOverrideUnchecked}`
: ''
}`}
/>
<div className="LemonSwitch__handle">{handleContent}</div>
</button>
)
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/scenes/activity/live/liveEventsTableLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const liveEventsTableLogic = kea<liveEventsTableLogicType>([
pollStats: true,
setStats: (stats) => ({ stats }),
showLiveStreamErrorToast: true,
addEventHost: (eventHost) => ({ eventHost }),
})),
reducers({
events: [
Expand Down Expand Up @@ -83,6 +84,17 @@ export const liveEventsTableLogic = kea<liveEventsTableLogicType>([
},
},
],
eventHosts: [
[] as string[],
{
addEventHost: (state, { eventHost }) => {
if (!state.includes(eventHost)) {
return [...state, eventHost]
}
return state
},
},
],
}),
selectors(({ selectors }) => ({
eventCount: [() => [selectors.events], (events: any) => events.length],
Expand Down Expand Up @@ -177,6 +189,17 @@ export const liveEventsTableLogic = kea<liveEventsTableLogicType>([
console.error('Failed to poll stats:', error)
}
},
addEvents: ({ events }) => {
if (events.length > 0) {
const event = events[0]
const eventUrl = event.properties?.$current_url
if (eventUrl) {
const eventHost = new URL(eventUrl).host
const eventProtocol = new URL(eventUrl).protocol
actions.addEventHost(`${eventProtocol}//${eventHost}`)
}
}
},
})),
events(({ actions, cache }) => ({
afterMount: () => {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/scenes/billing/billingLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ export const billingLogic = kea<billingLogicType>([
values.computedDiscount * 100,
await api.create('api/billing/credits/purchase', {
annual_amount_usd: +Math.round(+creditInput - +creditInput * values.creditDiscount),
discount_percent: values.computedDiscount * 100,
collection_method: collectionMethod,
})

Expand Down Expand Up @@ -502,8 +503,8 @@ export const billingLogic = kea<billingLogicType>([
errors: ({ creditInput, collectionMethod }) => ({
creditInput: !creditInput
? 'Please enter the amount of credits you want to purchase'
: // This value is used because 6666 - 10% = 6000
+creditInput < 6666
: // This value is used because 6667 - 10% = 6000
+creditInput < 6667
? 'Please enter a credit amount greater than $6,666'
: undefined,
collectionMethod: !collectionMethod ? 'Please select a collection method' : undefined,
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/scenes/onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ const ProductAnalyticsOnboarding = (): JSX.Element => {
// not sure if there is a better way to do this
useValues(newDashboardLogic)

const showTemplateSteps =
featureFlags[FEATURE_FLAGS.ONBOARDING_DASHBOARD_TEMPLATES] == 'test' && window.innerWidth > 1000

const options: ProductConfigOption[] = [
{
title: 'Autocapture frontend interactions',
Expand Down Expand Up @@ -165,10 +168,12 @@ const ProductAnalyticsOnboarding = (): JSX.Element => {
stepKey={OnboardingStepKey.INSTALL}
/>
<OnboardingProductConfiguration stepKey={OnboardingStepKey.PRODUCT_CONFIGURATION} options={options} />
{featureFlags[FEATURE_FLAGS.ONBOARDING_DASHBOARD_TEMPLATES] == 'test' ? (

{/* this is two conditionals because they need to be direct children of the wrapper */}
{showTemplateSteps ? (
<OnboardingDashboardTemplateSelectStep stepKey={OnboardingStepKey.DASHBOARD_TEMPLATE} />
) : null}
{featureFlags[FEATURE_FLAGS.ONBOARDING_DASHBOARD_TEMPLATES] == 'test' ? (
{showTemplateSteps ? (
<OnboardingDashboardTemplateConfigureStep stepKey={OnboardingStepKey.DASHBOARD_TEMPLATE_CONFIGURE} />
) : null}
</OnboardingWrapper>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/scenes/onboarding/onboardingLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { actions, connect, kea, listeners, path, props, reducers, selectors } fr
import { actionToUrl, router, urlToAction } from 'kea-router'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { liveEventsTableLogic } from 'scenes/activity/live/liveEventsTableLogic'
import { billingLogic } from 'scenes/billing/billingLogic'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { Scene } from 'scenes/sceneTypes'
Expand Down Expand Up @@ -110,6 +111,8 @@ export const getProductUri = (productKey: ProductKey): string => {
export const onboardingLogic = kea<onboardingLogicType>([
props({} as OnboardingLogicProps),
path(['scenes', 'onboarding', 'onboardingLogic']),
// connect this so we start collecting live events the whole time during onboarding
connect(liveEventsTableLogic),
connect({
values: [
billingLogic,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { iframedToolbarBrowserLogic } from 'lib/components/IframedToolbarBrowser
import { useEffect, useRef, useState } from 'react'
import { dashboardTemplateVariablesLogic } from 'scenes/dashboard/dashboardTemplateVariablesLogic'
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'

import { onboardingLogic, OnboardingStepKey } from '../onboardingLogic'
import { OnboardingStep } from '../OnboardingStep'
Expand All @@ -31,7 +32,7 @@ const UrlInput = ({ iframeRef }: { iframeRef: React.RefObject<HTMLIFrameElement>
const { browserUrl, currentPath } = useValues(
iframedToolbarBrowserLogic({ iframeRef, clearBrowserUrlOnUnmount: true })
)
const { snippetHosts } = useValues(sdksLogic)
const { combinedSnippetAndLiveEventsHosts } = useValues(sdksLogic)
const { addUrl } = useActions(authorizedUrlListLogic({ actionId: null, type: AuthorizedUrlListType.TOOLBAR_URLS }))
const [inputValue, setInputValue] = useState(currentPath)

Expand All @@ -56,7 +57,7 @@ const UrlInput = ({ iframeRef }: { iframeRef: React.RefObject<HTMLIFrameElement>
<LemonInputSelect
mode="single"
value={[browserUrl || 'my-website.com']}
options={snippetHosts.map((host) => ({ key: host, label: host }))}
options={combinedSnippetAndLiveEventsHosts.map((host) => ({ key: host, label: host }))}
allowCustomValues={false}
onChange={(v) => {
addUrl(v[0])
Expand Down Expand Up @@ -86,7 +87,9 @@ const UrlInput = ({ iframeRef }: { iframeRef: React.RefObject<HTMLIFrameElement>

export const SiteChooser = (): JSX.Element => {
const iframeRef = useRef<HTMLIFrameElement>(null)
const { snippetHosts, hasSnippetEventsLoading } = useValues(sdksLogic)
const { combinedSnippetAndLiveEventsHosts, hasSnippetEventsLoading } = useValues(sdksLogic)
const { setStepKey } = useActions(onboardingLogic)
const { isCloud } = useValues(preflightLogic)
const { setProposedBrowserUrl } = useActions(
iframedToolbarBrowserLogic({
iframeRef,
Expand All @@ -101,7 +104,6 @@ export const SiteChooser = (): JSX.Element => {
automaticallyAuthorizeBrowserUrl: true,
})
)
const { setStepKey } = useActions(onboardingLogic)

return (
<>
Expand All @@ -122,15 +124,21 @@ export const SiteChooser = (): JSX.Element => {
<h2>Select where you want to track events from.</h2>
{hasSnippetEventsLoading ? (
<Spinner />
) : snippetHosts.length > 0 ? (
) : combinedSnippetAndLiveEventsHosts.length > 0 ? (
<>
<p>
Not seeing the site you want?{' '}
<Link onClick={() => setStepKey(OnboardingStepKey.INSTALL)}>Install posthog-js</Link> or
Not seeing the site you want? Try clikcing around on your site to trigger a few events.
If you haven't yet,{' '}
<Link onClick={() => setStepKey(OnboardingStepKey.INSTALL)}>install posthog-js</Link> or
the HTML snippet wherever you want to track events, then come back here.
</p>
{isCloud && (
<p className="text-muted italic">
Note: Sites must be served over HTTPS to be selected.
</p>
)}
<div className="space-y-2">
{snippetHosts.map((host) => (
{combinedSnippetAndLiveEventsHosts.concat('https://posthog.com').map((host) => (
<LemonButton
key={`snippet-host-button-${host}`}
type="tertiary"
Expand Down Expand Up @@ -243,11 +251,11 @@ export const OnboardingDashboardTemplateConfigureStep = ({
</div>
) : (
<div className="grid grid-cols-6 space-x-6 min-h-[80vh]">
<div className="col-span-4 relative">
<div className="col-span-4 relative max-h-[100vh] overflow-y-hidden">
{browserUrl && iframeBanner?.level != 'error' ? (
<div className="border border-1 border-border-bold rounded h-full w-full flex flex-col">
<UrlInput iframeRef={iframeRef} />
<div className="m-2 grow rounded">
<div className="m-2 grow rounded ">
<IframedToolbarBrowser iframeRef={iframeRef} userIntent="add-action" />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { dashboardTemplateVariablesLogic } from 'scenes/dashboard/dashboardTempl
import { newDashboardLogic } from 'scenes/dashboard/newDashboardLogic'
import { urls } from 'scenes/urls'

import { sidePanelStateLogic } from '~/layout/navigation-3000/sidepanel/sidePanelStateLogic'
import { DashboardTemplateType, DashboardType } from '~/types'

import { onboardingLogic, OnboardingStepKey } from '../onboardingLogic'
Expand All @@ -30,6 +31,8 @@ export const onboardingTemplateConfigLogic = kea<onboardingTemplateConfigLogicTy
],
onboardingLogic,
['goToPreviousStep', 'setOnCompleteOnboardingRedirectUrl', 'goToNextStep'],
sidePanelStateLogic,
['closeSidePanel'],
],
}),
actions({
Expand Down Expand Up @@ -136,6 +139,9 @@ export const onboardingTemplateConfigLogic = kea<onboardingTemplateConfigLogicTy
actions.goToPreviousStep()
}
}
if (step === OnboardingStepKey.DASHBOARD_TEMPLATE) {
actions.closeSidePanel()
}
actions.setIsLoading(false)
},
})),
Expand Down
17 changes: 16 additions & 1 deletion frontend/src/scenes/onboarding/sdks/sdksLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { loaders } from 'kea-loaders'
import { urlToAction } from 'kea-router'
import api from 'lib/api'
import { LemonSelectOptions } from 'lib/lemon-ui/LemonSelect/LemonSelect'
import { liveEventsTableLogic } from 'scenes/activity/live/liveEventsTableLogic'

import { HogQLQuery, NodeKind } from '~/queries/schema'
import { hogql } from '~/queries/utils'
Expand Down Expand Up @@ -41,7 +42,7 @@ export const multiInstallProducts = [ProductKey.PRODUCT_ANALYTICS, ProductKey.FE
export const sdksLogic = kea<sdksLogicType>([
path(['scenes', 'onboarding', 'sdks', 'sdksLogic']),
connect({
values: [onboardingLogic, ['productKey']],
values: [onboardingLogic, ['productKey'], liveEventsTableLogic, ['eventHosts']],
}),
actions({
setSourceFilter: (sourceFilter: string | null) => ({ sourceFilter }),
Expand Down Expand Up @@ -118,6 +119,20 @@ export const sdksLogic = kea<sdksLogicType>([
return Object.keys(availableSDKInstructionsMap).length > 5 && sourceOptions.length > 2
},
],
combinedSnippetAndLiveEventsHosts: [
(selectors) => [selectors.snippetHosts, selectors.eventHosts],
(snippetHosts: string[], eventHosts: string[]): string[] => {
const combinedSnippetAndLiveEventsHosts = snippetHosts
for (const host of eventHosts) {
const hostProtocol = new URL(host).protocol
const currentProtocol = window.location.protocol
if (hostProtocol === currentProtocol && !combinedSnippetAndLiveEventsHosts.includes(host)) {
combinedSnippetAndLiveEventsHosts.push(host)
}
}
return combinedSnippetAndLiveEventsHosts
},
],
}),
loaders(({ actions }) => ({
hasSnippetEvents: [
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/scenes/settings/organization/Members.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,19 @@ export function Members(): JSX.Element | null {
},
sorter: (a, b) => a.joined_at.localeCompare(b.joined_at),
},
{
title: 'Last Logged In',
dataIndex: 'last_login',
key: 'last_login',
render: function RenderLastLogin(lastLogin) {
return (
<div className="whitespace-nowrap">
{lastLogin ? <TZLabel time={lastLogin as string} /> : 'Never'}
</div>
)
},
sorter: (a, b) => new Date(a.last_login ?? 0).getTime() - new Date(b.last_login ?? 0).getTime(),
},
{
key: 'actions',
width: 0,
Expand Down
22 changes: 13 additions & 9 deletions frontend/src/toolbar/actions/ActionAttribute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,19 @@ export function ActionAttribute({
return (
<div key={attribute} className="flex flex-row gap-2 justify-between items-center">
{automaticActionCreationEnabled && (
<LemonSwitch
size="small"
checked={automaticCreationIncludedPropertyKeys.includes(attribute)}
onChange={(checked) =>
checked
? addAutomaticCreationIncludedPropertyKey(attribute)
: removeAutomaticCreationIncludedPropertyKey(attribute)
}
/>
<>
<LemonSwitch
size="small"
checked={automaticCreationIncludedPropertyKeys.includes(attribute)}
onChange={(checked) =>
checked
? addAutomaticCreationIncludedPropertyKey(attribute)
: removeAutomaticCreationIncludedPropertyKey(attribute)
}
sliderColorOverrideChecked="primary-3000-light"
sliderColorOverrideUnchecked="muted-3000-light"
/>
</>
)}
<div className="text-muted text-xl">{icon}</div>
<div className="grow">{text}</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ export interface OrganizationDomainType {
export interface BaseMemberType {
id: string
user: UserBasicType
last_login: string | null
joined_at: string
updated_at: string
is_2fa_enabled: boolean
Expand Down
5 changes: 4 additions & 1 deletion posthog/api/organization_member.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import cast

from django.db.models import Model, Prefetch, QuerySet
from django.db.models import Model, Prefetch, QuerySet, F
from django.shortcuts import get_object_or_404
from django.views import View
from django_otp.plugins.otp_totp.models import TOTPDevice
Expand Down Expand Up @@ -42,6 +42,7 @@ class OrganizationMemberSerializer(serializers.ModelSerializer):
user = UserBasicSerializer(read_only=True)
is_2fa_enabled = serializers.SerializerMethodField()
has_social_auth = serializers.SerializerMethodField()
last_login = serializers.DateTimeField(read_only=True)

class Meta:
model = OrganizationMembership
Expand All @@ -53,6 +54,7 @@ class Meta:
"updated_at",
"is_2fa_enabled",
"has_social_auth",
"last_login",
]
read_only_fields = ["id", "joined_at", "updated_at"]

Expand Down Expand Up @@ -107,6 +109,7 @@ class OrganizationMemberViewSet(
),
Prefetch("user__social_auth", queryset=UserSocialAuth.objects.all()),
)
.annotate(last_login=F("user__last_login"))
)
lookup_field = "user__uuid"

Expand Down
Loading

0 comments on commit 54d798b

Please sign in to comment.