Skip to content

Commit

Permalink
Merge branch 'master' of github.com:PostHog/posthog into html-descrip…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
neilkakkar committed Oct 18, 2023
2 parents cb07d83 + 13fa0f2 commit 96a66b2
Show file tree
Hide file tree
Showing 47 changed files with 538 additions and 152 deletions.
Binary file modified frontend/__snapshots__/scenes-app-surveys--survey-templates.png
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.
Binary file modified frontend/__snapshots__/scenes-other-billing-v2--billing-v-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 10 additions & 4 deletions frontend/src/lib/components/BillingAlertsV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { LemonBanner } from 'lib/lemon-ui/LemonBanner'

export function BillingAlertsV2(): JSX.Element | null {
const { billingAlert } = useValues(billingLogic)
const { reportBillingAlertShown } = useActions(billingLogic)
const { reportBillingAlertShown, reportBillingAlertActionClicked } = useActions(billingLogic)
const { currentLocation } = useValues(router)
const [alertHidden, setAlertHidden] = useState(false)

Expand All @@ -21,21 +21,27 @@ export function BillingAlertsV2(): JSX.Element | null {
return null
}

const showButton = currentLocation.pathname !== urls.organizationBilling()
const showButton = billingAlert.contactSupport || currentLocation.pathname !== urls.organizationBilling()

const buttonProps = billingAlert.contactSupport
? {
to: 'mailto:[email protected]',
children: 'Contact support',
children: billingAlert.buttonCTA || 'Contact support',
onClick: () => reportBillingAlertActionClicked(billingAlert),
}
: {
to: urls.organizationBilling(),
children: 'Manage billing',
onClick: () => reportBillingAlertActionClicked(billingAlert),
}
: { to: urls.organizationBilling(), children: 'Manage billing' }

return (
<div className="my-4">
<LemonBanner
type={billingAlert.status}
action={showButton ? buttonProps : undefined}
onClose={billingAlert.status !== 'error' ? () => setAlertHidden(true) : undefined}
dismissKey={billingAlert.dismissKey}
>
<b>{billingAlert.title}</b>
<br />
Expand Down
1 change: 0 additions & 1 deletion frontend/src/models/cohortsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export const cohortsModel = kea<cohortsModelType>({
export_format: ExporterFormat.CSV,
export_context: {
path: `/api/cohort/${id}/persons`,
max_limit: 10000,
},
}
if (columns && columns.length > 0) {
Expand Down
40 changes: 23 additions & 17 deletions frontend/src/queries/nodes/DataTable/DataTableExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { IconExport } from 'lib/lemon-ui/icons'
import { triggerExport } from 'lib/components/ExportButton/exporter'
import { ExporterFormat } from '~/types'
import { DataNode, DataTableNode, NodeKind } from '~/queries/schema'
import { defaultDataTableColumns, extractExpressionComment } from '~/queries/nodes/DataTable/utils'
import {
defaultDataTableColumns,
extractExpressionComment,
removeExpressionComment,
} from '~/queries/nodes/DataTable/utils'
import { isEventsQuery, isHogQLQuery, isPersonsNode } from '~/queries/utils'
import { getPersonsEndpoint } from '~/queries/query'
import { ExportWithConfirmation } from '~/queries/nodes/DataTable/ExportWithConfirmation'
Expand All @@ -18,26 +22,28 @@ const EXPORT_MAX_LIMIT = 10000

function startDownload(query: DataTableNode, onlySelectedColumns: boolean): void {
const exportContext = isPersonsNode(query.source)
? { path: getPersonsEndpoint(query.source), max_limit: EXPORT_MAX_LIMIT }
: { source: query.source, max_limit: EXPORT_MAX_LIMIT }
? { path: getPersonsEndpoint(query.source) }
: { source: query.source }
if (!exportContext) {
throw new Error('Unsupported node type')
}

const columnMapping = {
url: ['properties.$current_url', 'properties.$screen_name'],
time: 'timestamp',
event: 'event',
source: 'properties.$lib',
person: isPersonsNode(query.source)
? ['distinct_ids.0', 'properties.email']
: ['person.distinct_ids.0', 'person.properties.email'],
}

if (onlySelectedColumns) {
exportContext['columns'] = (query.columns ?? defaultDataTableColumns(query.source.kind))
?.flatMap((c) => columnMapping[c] || c)
.filter((c) => c !== 'person.$delete')
exportContext['columns'] = (
(isEventsQuery(query.source) ? query.source.select : null) ??
query.columns ??
defaultDataTableColumns(query.source.kind)
)?.filter((c) => c !== 'person.$delete')

if (isEventsQuery(query.source)) {
exportContext['columns'] = exportContext['columns'].map((c: string) =>
removeExpressionComment(c) === 'person' ? 'person.properties.email' : c
)
} else if (isPersonsNode(query.source)) {
exportContext['columns'] = exportContext['columns'].map((c: string) =>
removeExpressionComment(c) === 'person' ? 'properties.email' : c
)
}
}
triggerExport({
export_format: ExporterFormat.CSV,
Expand Down Expand Up @@ -185,7 +191,7 @@ export function DataTableExport({ query }: DataTableExportProps): JSX.Element |
(isEventsQuery(source) || isPersonsNode(source) ? source.properties?.length || 0 : 0) +
(isEventsQuery(source) && source.event ? 1 : 0) +
(isPersonsNode(source) && source.search ? 1 : 0)
const canExportAllColumns = isEventsQuery(source) || isPersonsNode(source)
const canExportAllColumns = (isEventsQuery(source) && source.select.includes('*')) || isPersonsNode(source)
const showExportClipboardButtons = isPersonsNode(source) || isEventsQuery(source) || isHogQLQuery(source)

return (
Expand Down
1 change: 0 additions & 1 deletion frontend/src/queries/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ describe('query', () => {
'timestamp',
],
},
max_limit: 10000,
})
})

Expand Down
3 changes: 0 additions & 3 deletions frontend/src/queries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ import { currentSessionId } from 'lib/internalMetrics'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { FEATURE_FLAGS } from 'lib/constants'

const EXPORT_MAX_LIMIT = 10000

//get export context for a given query
export function queryExportContext<N extends DataNode = DataNode>(
query: N,
Expand All @@ -47,7 +45,6 @@ export function queryExportContext<N extends DataNode = DataNode>(
} else if (isEventsQuery(query) || isPersonsQuery(query)) {
return {
source: query,
max_limit: EXPORT_MAX_LIMIT,
}
} else if (isHogQLQuery(query)) {
return { source: query }
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/scenes/apps/frontendAppsLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { actions, afterMount, connect, defaults, kea, path, reducers } from 'kea
import type { frontendAppsLogicType } from './frontendAppsLogicType'
import { getAppContext } from 'lib/utils/getAppContext'
import { loaders } from 'kea-loaders'
import { FrontendApp, FrontendAppConfig, PluginConfigType } from '~/types'
import { FrontendApp, FrontendAppConfig } from '~/types'
import { frontendAppRequire } from './frontendAppRequire'
import { lemonToast } from 'lib/lemon-ui/lemonToast'
import { pluginsLogic } from 'scenes/plugins/pluginsLogic'
Expand Down Expand Up @@ -31,7 +31,7 @@ export const frontendAppsLogic = kea<frontendAppsLogicType>([
loadFrontendApp: async ({ id, pluginId, reload, attempt }) => {
if (!values.appConfigs[id]) {
if (pluginsLogic.findMounted()) {
const pluginConfig: PluginConfigType | undefined = pluginsLogic.values.getPluginConfig(id)
const pluginConfig = Object.values(pluginsLogic.values.pluginConfigs).find((c) => c.id === id)
const plugin = pluginConfig ? pluginsLogic.values.plugins[pluginConfig.plugin] : undefined
if (!plugin && !pluginConfig) {
throw Error(`Could not load metadata for app with ID ${id}`)
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/scenes/authentication/inviteSignupLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export const inviteSignupLogic = kea<inviteSignupLogicType>([
if (e.status === 400) {
if (e.code === 'invalid_recipient') {
actions.setError({ code: ErrorCodes.InvalidRecipient, detail: e.detail })
} else if (e.code === 'account_exists') {
location.href = e.detail
} else {
actions.setError({ code: ErrorCodes.InvalidInvite, detail: e.detail })
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/billing/Billing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export function Billing(): JSX.Element {
</div>
</div>

<div className="flex justify-between">
<div className="flex justify-between mt-4">
<h2>Products</h2>
{isOnboarding && upgradeAllProductsLink && (
<LemonButton
Expand Down
18 changes: 17 additions & 1 deletion frontend/src/scenes/billing/billing-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { convertAmountToUsage, convertUsageToAmount, projectUsage, summarizeUsage } from './billing-utils'
import {
convertAmountToUsage,
convertLargeNumberToWords,
convertUsageToAmount,
projectUsage,
summarizeUsage,
} from './billing-utils'
import tk from 'timekeeper'
import { dayjs } from 'lib/dayjs'
import billingJson from '~/mocks/fixtures/_billing_v2.json'
Expand Down Expand Up @@ -159,3 +165,13 @@ describe('convertAmountToUsageWithPercentDiscount', () => {
}
)
})

describe('convertLargeNumberToWords', () => {
it('should convert large numbers to words', () => {
expect(convertLargeNumberToWords(250, null, true, 'survey')).toEqual('First 250 surveys/mo')
expect(convertLargeNumberToWords(500, 250, true, 'survey')).toEqual('251-500')
expect(convertLargeNumberToWords(1000, 500, true, 'survey')).toEqual('501-1k')
expect(convertLargeNumberToWords(10000, 1000, true, 'survey')).toEqual('1-10k')
expect(convertLargeNumberToWords(10000000, 1000000, true, 'survey')).toEqual('1-10 million')
})
})
10 changes: 8 additions & 2 deletions frontend/src/scenes/billing/billing-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,20 @@ export const convertLargeNumberToWords = (
}

let denominator = 1

if (num >= 1000000) {
denominator = 1000000
} else if (num >= 1000) {
denominator = 1000
}

return `${previousNum ? `${(previousNum / denominator).toFixed(0)}-` : multipleTiers ? 'First ' : ''}${(
let prevDenominator = 1
if (previousNum && previousNum >= 1000000) {
prevDenominator = 1000000
} else if (previousNum && previousNum >= 1000) {
prevDenominator = 1000
}

return `${previousNum ? `${((previousNum + 1) / prevDenominator).toFixed(0)}-` : multipleTiers ? 'First ' : ''}${(
num / denominator
).toFixed(0)}${denominator === 1000000 ? ' million' : denominator === 1000 ? 'k' : ''}${
!previousNum && multipleTiers ? ` ${productType}s/mo` : ''
Expand Down
45 changes: 40 additions & 5 deletions frontend/src/scenes/billing/billingLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ export const ALLOCATION_THRESHOLD_BLOCK = 1.2 // Threshold to block usage
export interface BillingAlertConfig {
status: 'info' | 'warning' | 'error'
title: string
message: string
message?: string
contactSupport?: boolean
buttonCTA?: string
dismissKey?: string
}

const parseBillingResponse = (data: Partial<BillingV2Type>): BillingV2Type => {
Expand Down Expand Up @@ -54,6 +56,7 @@ export const billingLogic = kea<billingLogicType>([
actions({
setShowLicenseDirectInput: (show: boolean) => ({ show }),
reportBillingAlertShown: (alertConfig: BillingAlertConfig) => ({ alertConfig }),
reportBillingAlertActionClicked: (alertConfig: BillingAlertConfig) => ({ alertConfig }),
reportBillingV2Shown: true,
registerInstrumentationProps: true,
setRedirectPath: true,
Expand Down Expand Up @@ -130,9 +133,22 @@ export const billingLogic = kea<billingLogicType>([
(s) => [s.preflight, s.billing],
(preflight, billing): boolean => !!preflight?.is_debug && !billing?.billing_period,
],
projectedTotalAmountUsd: [
(s) => [s.billing],
(billing: BillingV2Type): number => {
if (!billing) {
return 0
}
let projectedTotal = 0
for (const product of billing.products || []) {
projectedTotal += parseFloat(product.projected_amount_usd || '0')
}
return projectedTotal
},
],
billingAlert: [
(s) => [s.billing, s.preflight],
(billing, preflight): BillingAlertConfig | undefined => {
(s) => [s.billing, s.preflight, s.projectedTotalAmountUsd],
(billing, preflight, projectedTotalAmountUsd): BillingAlertConfig | undefined => {
if (!billing || !preflight?.cloud) {
return
}
Expand Down Expand Up @@ -163,7 +179,7 @@ export const billingLogic = kea<billingLogicType>([
}
}

const productOverLimit = billing.products?.find((x) => {
const productOverLimit = billing.products?.find((x: BillingProductV2Type) => {
return x.percentage_usage > 1
})

Expand All @@ -190,6 +206,21 @@ export const billingLogic = kea<billingLogicType>([
)}% of your ${productApproachingLimit.usage_key.toLowerCase()} allocation.`,
}
}

if (
billing.current_total_amount_usd_after_discount &&
(parseFloat(billing.current_total_amount_usd_after_discount) > 1000 ||
projectedTotalAmountUsd > 1000) &&
billing.billing_period?.interval === 'month'
) {
return {
status: 'info',
title: `Switch to annual up-front billing to save up to 20% on your bill.`,
contactSupport: true,
buttonCTA: 'Contact sales',
dismissKey: 'annual-billing-cta',
}
}
},
],
}),
Expand Down Expand Up @@ -231,6 +262,11 @@ export const billingLogic = kea<billingLogicType>([
...alertConfig,
})
},
reportBillingAlertActionClicked: ({ alertConfig }) => {
posthog.capture('billing alert action clicked', {
...alertConfig,
})
},
loadBillingSuccess: () => {
if (
router.values.location.pathname.includes('/organization/billing') &&
Expand Down Expand Up @@ -276,7 +312,6 @@ export const billingLogic = kea<billingLogicType>([
}
},
})),

afterMount(({ actions }) => {
actions.loadBilling()
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function ReactEnvVarsSnippet(): JSX.Element {
return (
<CodeSnippet language={Language.Bash}>
{[
`REACT_APP_POSTHOG_PUBLIC_KEY=${currentTeam?.api_token}`,
`REACT_APP_PUBLIC_POSTHOG_KEY=${currentTeam?.api_token}`,
`REACT_APP_PUBLIC_POSTHOG_HOST=${window.location.origin}`,
].join('\n')}
</CodeSnippet>
Expand Down
1 change: 0 additions & 1 deletion frontend/src/scenes/persons/personsLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ export const personsLogic = kea<personsLogicType>({
path: cohort
? api.cohorts.determineListUrl(cohort, listFilters)
: api.persons.determineListUrl(listFilters),
max_limit: 10000,
},
},
],
Expand Down
Loading

0 comments on commit 96a66b2

Please sign in to comment.