diff --git a/ee/api/test/test_billing.py b/ee/api/test/test_billing.py index 2b4d38dd85bd8..1fd9293cfc04c 100644 --- a/ee/api/test/test_billing.py +++ b/ee/api/test/test_billing.py @@ -837,93 +837,3 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma self.organization.refresh_from_db() assert self.organization.customer_trust_scores == {"recordings": 0, "events": 15, "rows_synced": 0} - - -class TestActivateBillingAPI(APILicensedTest): - def test_activate_success(self): - url = "/api/billing-v2/activate" - data = {"products": "product_1:plan_1,product_2:plan_2", "redirect_path": "custom/path"} - - response = self.client.get(url, data=data) - self.assertEqual(response.status_code, status.HTTP_302_FOUND) - - self.assertIn("/activate", response.url) - self.assertIn("products=product_1:plan_1,product_2:plan_2", response.url) - url_pattern = r"redirect_uri=http://[^/]+/custom/path" - self.assertRegex(response.url, url_pattern) - - def test_deprecated_activation_success(self): - url = "/api/billing-v2/activate" - data = {"products": "product_1:plan_1,product_2:plan_2", "redirect_path": "custom/path"} - - response = self.client.get(url, data=data) - self.assertEqual(response.status_code, status.HTTP_302_FOUND) - - self.assertIn("/activate", response.url) - self.assertIn("products=product_1:plan_1,product_2:plan_2", response.url) - url_pattern = r"redirect_uri=http://[^/]+/custom/path" - self.assertRegex(response.url, url_pattern) - - def test_activate_with_default_redirect_path(self): - url = "/api/billing-v2/activate" - data = { - "products": "product_1:plan_1,product_2:plan_2", - } - - response = self.client.get(url, data) - - self.assertEqual(response.status_code, status.HTTP_302_FOUND) - self.assertIn("products=product_1:plan_1,product_2:plan_2", response.url) - url_pattern = r"redirect_uri=http://[^/]+/organization/billing" - self.assertRegex(response.url, url_pattern) - - def test_activate_failure(self): - url = "/api/billing-v2/activate" - data = {"none": "nothing"} - - response = self.client.get(url, data) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_activate_with_plan_error(self): - url = "/api/billing-v2/activate" - data = {"plan": "plan"} - - response = self.client.get(url, data) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "attr": "plan", - "code": "invalid_input", - "detail": "The 'plan' parameter is no longer supported. Please use the 'products' parameter instead.", - "type": "validation_error", - }, - ) - - @patch("ee.billing.billing_manager.BillingManager.deactivate_products") - @patch("ee.billing.billing_manager.BillingManager.get_billing") - def test_deactivate_success(self, mock_get_billing, mock_deactivate_products): - mock_deactivate_products.return_value = MagicMock() - mock_get_billing.return_value = { - "available_features": [], - "products": [], - } - - url = "/api/billing-v2/deactivate" - data = {"products": "product_1"} - - response = self.client.get(url, data) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - mock_deactivate_products.assert_called_once_with(self.organization, "product_1") - mock_get_billing.assert_called_once_with(self.organization, None) - - def test_deactivate_failure(self): - url = "/api/billing-v2/deactivate" - data = {"none": "nothing"} - - response = self.client.get(url, data) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/ee/clickhouse/views/test/__snapshots__/test_clickhouse_experiments.ambr b/ee/clickhouse/views/test/__snapshots__/test_clickhouse_experiments.ambr index b1eb79a39945c..389171ebc7e12 100644 --- a/ee/clickhouse/views/test/__snapshots__/test_clickhouse_experiments.ambr +++ b/ee/clickhouse/views/test/__snapshots__/test_clickhouse_experiments.ambr @@ -441,26 +441,97 @@ # --- # name: ClickhouseTestFunnelExperimentResults.test_experiment_flow_with_event_results_for_three_test_variants.1 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + /* user_id:0 request:_snapshot_ */ + SELECT array(replaceRegexpAll(JSONExtractRaw(properties, '$feature/a-b-test'), '^"|"$', '')) AS value, + count(*) as count + FROM events e + WHERE team_id = 2 + AND event IN ['$pageleave', '$pageview'] + AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC') + AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-06 00:00:00', 'UTC') + GROUP BY value + ORDER BY count DESC, value DESC + LIMIT 26 + OFFSET 0 ''' # --- # name: ClickhouseTestFunnelExperimentResults.test_experiment_flow_with_event_results_for_three_test_variants.2 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + /* user_id:0 request:_snapshot_ */ + SELECT countIf(steps = 1) step_1, + countIf(steps = 2) step_2, + avg(step_1_average_conversion_time_inner) step_1_average_conversion_time, + median(step_1_median_conversion_time_inner) step_1_median_conversion_time, + prop + FROM + (SELECT aggregation_target, + steps, + avg(step_1_conversion_time) step_1_average_conversion_time_inner, + median(step_1_conversion_time) step_1_median_conversion_time_inner , + prop + FROM + (SELECT aggregation_target, + steps, + max(steps) over (PARTITION BY aggregation_target, + prop) as max_steps, + step_1_conversion_time , + prop + FROM + (SELECT *, + if(latest_0 <= latest_1 + AND latest_1 <= latest_0 + INTERVAL 14 DAY, 2, 1) AS steps , + if(isNotNull(latest_1) + AND latest_1 <= latest_0 + INTERVAL 14 DAY, dateDiff('second', toDateTime(latest_0), toDateTime(latest_1)), NULL) step_1_conversion_time, + prop + FROM + (SELECT aggregation_target, timestamp, step_0, + latest_0, + step_1, + min(latest_1) over (PARTITION by aggregation_target, + prop + ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 0 PRECEDING) latest_1 , + if(has([[''], ['test_1'], ['test'], ['control'], ['unknown_3'], ['unknown_2'], ['unknown_1'], ['test_2']], prop), prop, ['Other']) as prop + FROM + (SELECT *, + if(notEmpty(arrayFilter(x -> notEmpty(x), prop_vals)), prop_vals, ['']) as prop + FROM + (SELECT e.timestamp as timestamp, + pdi.person_id as aggregation_target, + pdi.person_id as person_id, + if(event = '$pageview', 1, 0) as step_0, + if(step_0 = 1, timestamp, null) as latest_0, + if(event = '$pageleave', 1, 0) as step_1, + if(step_1 = 1, timestamp, null) as latest_1, + array(replaceRegexpAll(JSONExtractRaw(properties, '$feature/a-b-test'), '^"|"$', '')) AS prop_basic, + prop_basic as prop, + argMinIf(prop, timestamp, notEmpty(arrayFilter(x -> notEmpty(x), prop))) over (PARTITION by aggregation_target) as prop_vals + FROM events e + INNER JOIN + (SELECT distinct_id, + argMax(person_id, version) as person_id + FROM person_distinct_id2 + WHERE team_id = 2 + AND distinct_id IN + (SELECT distinct_id + FROM events + WHERE team_id = 2 + AND event IN ['$pageleave', '$pageview'] + AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC') + AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-06 00:00:00', 'UTC') ) + GROUP BY distinct_id + HAVING argMax(is_deleted, version) = 0) AS pdi ON e.distinct_id = pdi.distinct_id + WHERE team_id = 2 + AND event IN ['$pageleave', '$pageview'] + AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC') + AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-06 00:00:00', 'UTC') + AND (step_0 = 1 + OR step_1 = 1) ))) + WHERE step_0 = 1 )) + GROUP BY aggregation_target, + steps, + prop + HAVING steps = max_steps) + GROUP BY prop ''' # --- # name: ClickhouseTestFunnelExperimentResults.test_experiment_flow_with_event_results_for_three_test_variants.3 @@ -584,26 +655,97 @@ # --- # name: ClickhouseTestFunnelExperimentResults.test_experiment_flow_with_event_results_with_hogql_aggregation.1 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + /* user_id:0 request:_snapshot_ */ + SELECT array(replaceRegexpAll(JSONExtractRaw(properties, '$feature/a-b-test'), '^"|"$', '')) AS value, + count(*) as count + FROM events e + WHERE team_id = 2 + AND event IN ['$pageleave', '$pageview'] + AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC') + AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-06 00:00:00', 'UTC') + GROUP BY value + ORDER BY count DESC, value DESC + LIMIT 26 + OFFSET 0 ''' # --- # name: ClickhouseTestFunnelExperimentResults.test_experiment_flow_with_event_results_with_hogql_aggregation.2 ''' - /* celery:posthog.tasks.tasks.sync_insight_caching_state */ - SELECT team_id, - date_diff('second', max(timestamp), now()) AS age - FROM events - WHERE timestamp > date_sub(DAY, 3, now()) - AND timestamp < now() - GROUP BY team_id - ORDER BY age; + /* user_id:0 request:_snapshot_ */ + SELECT countIf(steps = 1) step_1, + countIf(steps = 2) step_2, + avg(step_1_average_conversion_time_inner) step_1_average_conversion_time, + median(step_1_median_conversion_time_inner) step_1_median_conversion_time, + prop + FROM + (SELECT aggregation_target, + steps, + avg(step_1_conversion_time) step_1_average_conversion_time_inner, + median(step_1_conversion_time) step_1_median_conversion_time_inner , + prop + FROM + (SELECT aggregation_target, + steps, + max(steps) over (PARTITION BY aggregation_target, + prop) as max_steps, + step_1_conversion_time , + prop + FROM + (SELECT *, + if(latest_0 <= latest_1 + AND latest_1 <= latest_0 + INTERVAL 14 DAY, 2, 1) AS steps , + if(isNotNull(latest_1) + AND latest_1 <= latest_0 + INTERVAL 14 DAY, dateDiff('second', toDateTime(latest_0), toDateTime(latest_1)), NULL) step_1_conversion_time, + prop + FROM + (SELECT aggregation_target, timestamp, step_0, + latest_0, + step_1, + min(latest_1) over (PARTITION by aggregation_target, + prop + ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 0 PRECEDING) latest_1 , + if(has([['test'], ['control'], ['']], prop), prop, ['Other']) as prop + FROM + (SELECT *, + if(notEmpty(arrayFilter(x -> notEmpty(x), prop_vals)), prop_vals, ['']) as prop + FROM + (SELECT e.timestamp as timestamp, + replaceRegexpAll(nullIf(nullIf(JSONExtractRaw(properties, '$account_id'), ''), 'null'), '^"|"$', '') as aggregation_target, + pdi.person_id as person_id, + if(event = '$pageview', 1, 0) as step_0, + if(step_0 = 1, timestamp, null) as latest_0, + if(event = '$pageleave', 1, 0) as step_1, + if(step_1 = 1, timestamp, null) as latest_1, + array(replaceRegexpAll(JSONExtractRaw(properties, '$feature/a-b-test'), '^"|"$', '')) AS prop_basic, + prop_basic as prop, + argMinIf(prop, timestamp, notEmpty(arrayFilter(x -> notEmpty(x), prop))) over (PARTITION by aggregation_target) as prop_vals + FROM events e + INNER JOIN + (SELECT distinct_id, + argMax(person_id, version) as person_id + FROM person_distinct_id2 + WHERE team_id = 2 + AND distinct_id IN + (SELECT distinct_id + FROM events + WHERE team_id = 2 + AND event IN ['$pageleave', '$pageview'] + AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC') + AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-06 00:00:00', 'UTC') ) + GROUP BY distinct_id + HAVING argMax(is_deleted, version) = 0) AS pdi ON e.distinct_id = pdi.distinct_id + WHERE team_id = 2 + AND event IN ['$pageleave', '$pageview'] + AND toTimeZone(timestamp, 'UTC') >= toDateTime('2020-01-01 00:00:00', 'UTC') + AND toTimeZone(timestamp, 'UTC') <= toDateTime('2020-01-06 00:00:00', 'UTC') + AND (step_0 = 1 + OR step_1 = 1) ))) + WHERE step_0 = 1 )) + GROUP BY aggregation_target, + steps, + prop + HAVING steps = max_steps) + GROUP BY prop ''' # --- # name: ClickhouseTestFunnelExperimentResults.test_experiment_flow_with_event_results_with_hogql_aggregation.3 diff --git a/ee/urls.py b/ee/urls.py index 3cebde01fe365..0a5e0d9b63855 100644 --- a/ee/urls.py +++ b/ee/urls.py @@ -34,7 +34,6 @@ def extend_api_router( project_feature_flags_router: NestedRegistryItem, ) -> None: root_router.register(r"billing", billing.BillingViewset, "billing") - root_router.register(r"billing-v2", billing.BillingViewset, "billing") # Legacy transition route root_router.register(r"license", license.LicenseViewSet) root_router.register(r"time_to_see_data", time_to_see_data.TimeToSeeDataViewSet, "query_metrics") root_router.register(r"integrations", integration.PublicIntegrationViewSet) diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png index 26bb0b6f5e96f..43a834a6911c2 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-edit--light.png differ diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing--dark.png b/frontend/__snapshots__/scenes-other-billing-v2--billing--dark.png new file mode 100644 index 0000000000000..ad4b61f55a87a Binary files /dev/null and b/frontend/__snapshots__/scenes-other-billing-v2--billing--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing--light.png b/frontend/__snapshots__/scenes-other-billing-v2--billing--light.png new file mode 100644 index 0000000000000..4f956d155b6c3 Binary files /dev/null and b/frontend/__snapshots__/scenes-other-billing-v2--billing--light.png differ diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing-with-discount--dark.png b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-discount--dark.png new file mode 100644 index 0000000000000..11fa60342f498 Binary files /dev/null and b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-discount--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing-with-discount--light.png b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-discount--light.png new file mode 100644 index 0000000000000..54b9a451c794d Binary files /dev/null and b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-discount--light.png differ diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing-with-limit-and-100-percent-discount--dark.png b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-limit-and-100-percent-discount--dark.png new file mode 100644 index 0000000000000..70caced4d1b3d Binary files /dev/null and b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-limit-and-100-percent-discount--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing-with-limit-and-100-percent-discount--light.png b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-limit-and-100-percent-discount--light.png new file mode 100644 index 0000000000000..b8f13d3bd3afc Binary files /dev/null and b/frontend/__snapshots__/scenes-other-billing-v2--billing-with-limit-and-100-percent-discount--light.png differ diff --git a/frontend/src/lib/components/ConfirmUpgradeModal/confirmUpgradeModalLogic.ts b/frontend/src/lib/components/ConfirmUpgradeModal/confirmUpgradeModalLogic.ts index 48016a916b56e..a004d0a6a0e56 100644 --- a/frontend/src/lib/components/ConfirmUpgradeModal/confirmUpgradeModalLogic.ts +++ b/frontend/src/lib/components/ConfirmUpgradeModal/confirmUpgradeModalLogic.ts @@ -1,6 +1,6 @@ import { actions, kea, listeners, path, reducers } from 'kea' -import { BillingV2PlanType } from '~/types' +import { BillingPlanType } from '~/types' import type { confirmUpgradeModalLogicType } from './confirmUpgradeModalLogicType' @@ -8,7 +8,7 @@ export const confirmUpgradeModalLogic = kea([ path(['lib', 'components', 'ConfirmUpgradeModal', 'confirmUpgradeModalLogic']), actions({ showConfirmUpgradeModal: ( - upgradePlan: BillingV2PlanType, + upgradePlan: BillingPlanType, confirmCallback: () => void, cancelCallback: () => void ) => ({ @@ -22,7 +22,7 @@ export const confirmUpgradeModalLogic = kea([ }), reducers({ upgradePlan: [ - null as BillingV2PlanType | null, + null as BillingPlanType | null, { showConfirmUpgradeModal: (_, { upgradePlan }) => upgradePlan, hideConfirmUpgradeModal: () => null, diff --git a/frontend/src/lib/components/PayGateMini/PayGateButton.tsx b/frontend/src/lib/components/PayGateMini/PayGateButton.tsx index 26790cf7d138e..2f13b85e24c1c 100644 --- a/frontend/src/lib/components/PayGateMini/PayGateButton.tsx +++ b/frontend/src/lib/components/PayGateMini/PayGateButton.tsx @@ -4,14 +4,14 @@ import { FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic, FeatureFlagsSet } from 'lib/logic/featureFlagLogic' import { urls } from 'scenes/urls' -import { BillingProductV2AddonType, BillingProductV2Type, BillingV2FeatureType, BillingV2Type } from '~/types' +import { BillingFeatureType, BillingProductV2AddonType, BillingProductV2Type, BillingType } from '~/types' interface PayGateButtonProps { gateVariant: 'add-card' | 'contact-sales' | 'move-to-cloud' | null productWithFeature: BillingProductV2AddonType | BillingProductV2Type - featureInfo: BillingV2FeatureType + featureInfo: BillingFeatureType onCtaClick: () => void - billing: BillingV2Type | null + billing: BillingType | null isAddonProduct?: boolean scrollToProduct: boolean } @@ -50,9 +50,9 @@ export const PayGateButton = ({ const getCtaLink = ( gateVariant: 'add-card' | 'contact-sales' | 'move-to-cloud' | null, productWithFeature: BillingProductV2AddonType | BillingProductV2Type, - featureInfo: BillingV2FeatureType, + featureInfo: BillingFeatureType, featureFlags: FeatureFlagsSet, - subscriptionLevel?: BillingV2Type['subscription_level'], + subscriptionLevel?: BillingType['subscription_level'], isAddonProduct?: boolean, scrollToProduct: boolean = true ): string | undefined => { @@ -77,7 +77,7 @@ const getCtaLink = ( const getCtaLabel = ( gateVariant: 'add-card' | 'contact-sales' | 'move-to-cloud' | null, - billing: BillingV2Type | null, + billing: BillingType | null, featureFlags: FeatureFlagsSet ): string => { if ( diff --git a/frontend/src/lib/components/PayGateMini/PayGateMini.tsx b/frontend/src/lib/components/PayGateMini/PayGateMini.tsx index c76e4a7c030d9..58f09073b783c 100644 --- a/frontend/src/lib/components/PayGateMini/PayGateMini.tsx +++ b/frontend/src/lib/components/PayGateMini/PayGateMini.tsx @@ -12,10 +12,10 @@ import { getProductIcon } from 'scenes/products/Products' import { AvailableFeature, + BillingFeatureType, BillingProductV2AddonType, BillingProductV2Type, - BillingV2FeatureType, - BillingV2Type, + BillingType, } from '~/types' import { upgradeModalLogic } from '../UpgradeModal/upgradeModalLogic' @@ -139,14 +139,14 @@ export function PayGateMini({ interface PayGateContentProps { className?: string background: boolean - featureInfo: BillingV2FeatureType - featureAvailableOnOrg?: BillingV2FeatureType | null + featureInfo: BillingFeatureType + featureAvailableOnOrg?: BillingFeatureType | null gateVariant: 'add-card' | 'contact-sales' | 'move-to-cloud' | null productWithFeature: BillingProductV2AddonType | BillingProductV2Type isGrandfathered?: boolean isAddonProduct?: boolean - billing: BillingV2Type | null - featureInfoOnNextPlan?: BillingV2FeatureType + billing: BillingType | null + featureInfoOnNextPlan?: BillingFeatureType children: React.ReactNode handleCtaClick: () => void } @@ -197,12 +197,12 @@ function PayGateContent({ } const renderUsageLimitMessage = ( - featureAvailableOnOrg: BillingV2FeatureType | null | undefined, - featureInfoOnNextPlan: BillingV2FeatureType | undefined, + featureAvailableOnOrg: BillingFeatureType | null | undefined, + featureInfoOnNextPlan: BillingFeatureType | undefined, gateVariant: 'add-card' | 'contact-sales' | 'move-to-cloud' | null, - featureInfo: BillingV2FeatureType, + featureInfo: BillingFeatureType, productWithFeature: BillingProductV2AddonType | BillingProductV2Type, - billing: BillingV2Type | null, + billing: BillingType | null, featureFlags: FeatureFlagsSet, isAddonProduct?: boolean, handleCtaClick?: () => void @@ -263,7 +263,7 @@ const renderUsageLimitMessage = ( const renderGateVariantMessage = ( gateVariant: 'add-card' | 'contact-sales' | 'move-to-cloud' | null, productWithFeature: BillingProductV2AddonType | BillingProductV2Type, - billing: BillingV2Type | null, + billing: BillingType | null, featureFlags: FeatureFlagsSet, isAddonProduct?: boolean ): JSX.Element => { diff --git a/frontend/src/mocks/features.ts b/frontend/src/mocks/features.ts index 10d4c23a7bb51..9ea2f81d6e530 100644 --- a/frontend/src/mocks/features.ts +++ b/frontend/src/mocks/features.ts @@ -1,10 +1,10 @@ -import { AvailableFeature, BillingV2FeatureType } from '~/types' +import { AvailableFeature, BillingFeatureType } from '~/types' let features: AvailableFeature[] = [] export const useAvailableFeatures = (f: AvailableFeature[]): void => { features = f } -export const getAvailableProductFeatures = (): BillingV2FeatureType[] => { +export const getAvailableProductFeatures = (): BillingFeatureType[] => { return features.map((feature) => { return { key: feature, diff --git a/frontend/src/mocks/fixtures/_billing.tsx b/frontend/src/mocks/fixtures/_billing.tsx index f8fa0010554ff..b5332d2b24aa1 100644 --- a/frontend/src/mocks/fixtures/_billing.tsx +++ b/frontend/src/mocks/fixtures/_billing.tsx @@ -1,8 +1,8 @@ import { dayjs } from 'lib/dayjs' -import { BillingV2Type } from '~/types' +import { BillingType } from '~/types' -export const billingJson: BillingV2Type = { +export const billingJson: BillingType = { customer_id: 'cus_Pg7PIL8MsKi6bx', deactivated: false, has_active_subscription: true, diff --git a/frontend/src/scenes/billing/AllProductsPlanComparison.tsx b/frontend/src/scenes/billing/AllProductsPlanComparison.tsx index 8809851118b84..2f6c7171acb68 100644 --- a/frontend/src/scenes/billing/AllProductsPlanComparison.tsx +++ b/frontend/src/scenes/billing/AllProductsPlanComparison.tsx @@ -11,7 +11,7 @@ import React, { useState } from 'react' import { getProductIcon } from 'scenes/products/Products' import useResizeObserver from 'use-resize-observer' -import { BillingProductV2AddonType, BillingProductV2Type, BillingV2FeatureType, BillingV2PlanType } from '~/types' +import { BillingFeatureType, BillingPlanType, BillingProductV2AddonType, BillingProductV2Type } from '~/types' import { convertLargeNumberToWords, getProration, getProrationMessage, getUpgradeProductLink } from './billing-utils' import { billingLogic } from './billingLogic' @@ -23,7 +23,7 @@ export function PlanIcon({ className, timeDenominator, }: { - feature?: BillingV2FeatureType + feature?: BillingFeatureType className?: string timeDenominator?: string }): JSX.Element { @@ -56,7 +56,7 @@ const PricingTiers = ({ plan, product, }: { - plan: BillingV2PlanType + plan: BillingPlanType product: BillingProductV2Type | BillingProductV2AddonType }): JSX.Element => { const { width, ref: tiersRef } = useResizeObserver() @@ -114,7 +114,7 @@ const PricingTiers = ({ * @param {string} plan.included_if - Condition for plan inclusion. * @returns {string} - The pricing description for the plan. */ -function getPlanDescription(plan: BillingV2PlanType): string { +function getPlanDescription(plan: BillingPlanType): string { if (plan.free_allocation && !plan.tiers) { return 'Free forever' } else if (plan.unit_amount_usd) { @@ -283,7 +283,7 @@ export const AllProductsPlanComparison = ({ {includedPlans - .find((plan: BillingV2PlanType) => plan.included_if == 'has_subscription') + .find((plan: BillingPlanType) => plan.included_if == 'has_subscription') ?.features?.map((feature) => ( // Inclusion product feature row @@ -521,13 +521,7 @@ export const AllProductsPlanComparisonModal = ({ ) } -const AddonPlanTiers = ({ - plan, - addon, -}: { - plan: BillingV2PlanType - addon: BillingProductV2AddonType -}): JSX.Element => { +const AddonPlanTiers = ({ plan, addon }: { plan: BillingPlanType; addon: BillingProductV2AddonType }): JSX.Element => { const [showTiers, setShowTiers] = useState(false) return showTiers ? ( diff --git a/frontend/src/scenes/billing/Billing.stories.tsx b/frontend/src/scenes/billing/Billing.stories.tsx index 139cc156c4bc1..43ce5c0aa362d 100644 --- a/frontend/src/scenes/billing/Billing.stories.tsx +++ b/frontend/src/scenes/billing/Billing.stories.tsx @@ -32,7 +32,7 @@ const meta: Meta = { ], } export default meta -export const _BillingV2 = (): JSX.Element => { +export const _Billing = (): JSX.Element => { useStorybookMocks({ get: { '/api/billing/': { @@ -44,7 +44,7 @@ export const _BillingV2 = (): JSX.Element => { return } -export const BillingV2WithDiscount = (): JSX.Element => { +export const BillingWithDiscount = (): JSX.Element => { useStorybookMocks({ get: { '/api/billing/': { @@ -56,7 +56,7 @@ export const BillingV2WithDiscount = (): JSX.Element => { return } -export const BillingV2WithLimitAnd100PercentDiscount = (): JSX.Element => { +export const BillingWithLimitAnd100PercentDiscount = (): JSX.Element => { useStorybookMocks({ get: { '/api/billing/': { diff --git a/frontend/src/scenes/billing/Billing.tsx b/frontend/src/scenes/billing/Billing.tsx index 7052186af73f6..084dd201ced20 100644 --- a/frontend/src/scenes/billing/Billing.tsx +++ b/frontend/src/scenes/billing/Billing.tsx @@ -43,7 +43,7 @@ export function Billing(): JSX.Element { isAnnualPlan, billingError, } = useValues(billingLogic) - const { reportBillingV2Shown } = useActions(billingLogic) + const { reportBillingShown } = useActions(billingLogic) const { preflight, isCloudOrDev } = useValues(preflightLogic) const { openSupportForm } = useActions(supportLogic) const { featureFlags } = useValues(featureFlagLogic) @@ -54,7 +54,7 @@ export function Billing(): JSX.Element { useEffect(() => { if (billing) { - reportBillingV2Shown() + reportBillingShown() } }, [!!billing]) diff --git a/frontend/src/scenes/billing/BillingProduct.tsx b/frontend/src/scenes/billing/BillingProduct.tsx index be083745e9fea..8654f18d72c17 100644 --- a/frontend/src/scenes/billing/BillingProduct.tsx +++ b/frontend/src/scenes/billing/BillingProduct.tsx @@ -15,7 +15,7 @@ import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { useRef } from 'react' import { getProductIcon } from 'scenes/products/Products' -import { BillingProductV2AddonType, BillingProductV2Type, BillingV2TierType } from '~/types' +import { BillingProductV2AddonType, BillingProductV2Type, BillingTierType } from '~/types' import { convertLargeNumberToWords, getUpgradeProductLink, summarizeUsage } from './billing-utils' import { BillingGauge } from './BillingGauge' @@ -29,7 +29,7 @@ import { ProductPricingModal } from './ProductPricingModal' import { UnsubscribeSurveyModal } from './UnsubscribeSurveyModal' export const getTierDescription = ( - tiers: BillingV2TierType[], + tiers: BillingTierType[], i: number, product: BillingProductV2Type | BillingProductV2AddonType, interval: string diff --git a/frontend/src/scenes/billing/PlanComparison.tsx b/frontend/src/scenes/billing/PlanComparison.tsx index 741609879ffd2..c12dd34f96f0f 100644 --- a/frontend/src/scenes/billing/PlanComparison.tsx +++ b/frontend/src/scenes/billing/PlanComparison.tsx @@ -13,7 +13,7 @@ import React, { useState } from 'react' import { getProductIcon } from 'scenes/products/Products' import useResizeObserver from 'use-resize-observer' -import { BillingProductV2AddonType, BillingProductV2Type, BillingV2FeatureType, BillingV2PlanType } from '~/types' +import { BillingFeatureType, BillingPlanType, BillingProductV2AddonType, BillingProductV2Type } from '~/types' import { convertLargeNumberToWords, getProration, getUpgradeProductLink } from './billing-utils' import { billingLogic } from './billingLogic' @@ -25,7 +25,7 @@ export function PlanIcon({ className, timeDenominator, }: { - feature?: BillingV2FeatureType + feature?: BillingFeatureType className?: string timeDenominator?: string }): JSX.Element { @@ -58,7 +58,7 @@ const PricingTiers = ({ plan, product, }: { - plan: BillingV2PlanType + plan: BillingPlanType product: BillingProductV2Type | BillingProductV2AddonType }): JSX.Element => { const { width, ref: tiersRef } = useResizeObserver() @@ -426,7 +426,7 @@ export const PlanComparison = ({ {includedPlans - .find((plan: BillingV2PlanType) => plan.included_if == 'has_subscription') + .find((plan: BillingPlanType) => plan.included_if == 'has_subscription') ?.features?.map((feature, i) => ( { +const AddonPlanTiers = ({ plan, addon }: { plan: BillingPlanType; addon: BillingProductV2AddonType }): JSX.Element => { const [showTiers, setShowTiers] = useState(false) return showTiers ? ( diff --git a/frontend/src/scenes/billing/ProductPricingModal.tsx b/frontend/src/scenes/billing/ProductPricingModal.tsx index fc372d232d3e7..49e3356371710 100644 --- a/frontend/src/scenes/billing/ProductPricingModal.tsx +++ b/frontend/src/scenes/billing/ProductPricingModal.tsx @@ -1,7 +1,7 @@ import { LemonModal } from '@posthog/lemon-ui' import { capitalizeFirstLetter } from 'lib/utils' -import { BillingProductV2AddonType, BillingProductV2Type, BillingV2PlanType } from '~/types' +import { BillingPlanType, BillingProductV2AddonType, BillingProductV2Type } from '~/types' import { getTierDescription } from './BillingProduct' @@ -19,7 +19,7 @@ export const ProductPricingModal = ({ if (!planKey) { return null } - const tiers = product?.plans?.find((plan: BillingV2PlanType) => plan.plan_key === planKey)?.tiers + const tiers = product?.plans?.find((plan: BillingPlanType) => plan.plan_key === planKey)?.tiers if (!product || !tiers) { return null diff --git a/frontend/src/scenes/billing/billing-utils.ts b/frontend/src/scenes/billing/billing-utils.ts index 49457eb9a6a16..e0098f710403b 100644 --- a/frontend/src/scenes/billing/billing-utils.ts +++ b/frontend/src/scenes/billing/billing-utils.ts @@ -2,7 +2,7 @@ import { FEATURE_FLAGS } from 'lib/constants' import { dayjs } from 'lib/dayjs' import { FeatureFlagsSet } from 'lib/logic/featureFlagLogic' -import { BillingProductV2Type, BillingV2TierType, BillingV2Type } from '~/types' +import { BillingProductV2Type, BillingTierType, BillingType } from '~/types' export const summarizeUsage = (usage: number | null): string => { if (usage === null) { @@ -15,10 +15,7 @@ export const summarizeUsage = (usage: number | null): string => { return `${Math.round(usage / 1000000)} million` } -export const projectUsage = ( - usage: number | undefined, - period: BillingV2Type['billing_period'] -): number | undefined => { +export const projectUsage = (usage: number | undefined, period: BillingType['billing_period']): number | undefined => { if (typeof usage === 'undefined') { return usage } @@ -39,7 +36,7 @@ export const projectUsage = ( export const convertUsageToAmount = ( usage: number, - productAndAddonTiers: BillingV2TierType[][], + productAndAddonTiers: BillingTierType[][], percentDiscount?: number ): string => { if (!productAndAddonTiers) { @@ -47,7 +44,7 @@ export const convertUsageToAmount = ( } let remainingUsage = usage let amount = 0 - let previousTier: BillingV2TierType | undefined = undefined + let previousTier: BillingTierType | undefined = undefined const tiers = productAndAddonTiers[0].map((tier, index) => { const allAddonsTiers = productAndAddonTiers.slice(1) @@ -91,7 +88,7 @@ export const convertUsageToAmount = ( export const convertAmountToUsage = ( amount: string, - productAndAddonTiers: BillingV2TierType[][], + productAndAddonTiers: BillingTierType[][], discountPercent?: number ): number => { if (!amount) { @@ -118,7 +115,7 @@ export const convertAmountToUsage = ( let remainingAmount = parseFloat(amount) let usage = 0 - let previousTier: BillingV2TierType | undefined = undefined + let previousTier: BillingTierType | undefined = undefined if (remainingAmount === 0) { if (parseFloat(tiers[0].unit_amount_usd) === 0) { @@ -172,7 +169,7 @@ export const getUpgradeProductLink = ({ upgradeToPlanKey: string redirectPath?: string includeAddons: boolean - subscriptionLevel?: BillingV2Type['subscription_level'] + subscriptionLevel?: BillingType['subscription_level'] featureFlags: FeatureFlagsSet }): string => { let url = '/api/billing/activate?' diff --git a/frontend/src/scenes/billing/billingLogic.tsx b/frontend/src/scenes/billing/billingLogic.tsx index 30604688729a5..ec2d3718bc61d 100644 --- a/frontend/src/scenes/billing/billingLogic.tsx +++ b/frontend/src/scenes/billing/billingLogic.tsx @@ -15,7 +15,7 @@ import posthog from 'posthog-js' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' import { userLogic } from 'scenes/userLogic' -import { BillingProductV2Type, BillingV2PlanType, BillingV2Type, ProductKey } from '~/types' +import { BillingPlanType, BillingProductV2Type, BillingType, ProductKey } from '~/types' import type { billingLogicType } from './billingLogicType' @@ -51,7 +51,7 @@ export interface BillingError { action: LemonButtonPropsBase } -const parseBillingResponse = (data: Partial): BillingV2Type => { +const parseBillingResponse = (data: Partial): BillingType => { if (data.billing_period) { data.billing_period = { current_period_start: dayjs(data.billing_period.current_period_start), @@ -72,7 +72,7 @@ const parseBillingResponse = (data: Partial): BillingV2Type => { data.amount_off_expires_at = data.billing_period.current_period_end } - return data as BillingV2Type + return data as BillingType } export const billingLogic = kea([ @@ -83,7 +83,7 @@ export const billingLogic = kea([ setShowLicenseDirectInput: (show: boolean) => ({ show }), reportBillingAlertShown: (alertConfig: BillingAlertConfig) => ({ alertConfig }), reportBillingAlertActionClicked: (alertConfig: BillingAlertConfig) => ({ alertConfig }), - reportBillingV2Shown: true, + reportBillingShown: true, registerInstrumentationProps: true, setRedirectPath: true, setIsOnboarding: true, @@ -182,7 +182,7 @@ export const billingLogic = kea([ }), loaders(({ actions, values }) => ({ billing: [ - null as BillingV2Type | null, + null as BillingType | null, { loadBilling: async () => { const response = await api.get('api/billing') @@ -298,7 +298,7 @@ export const billingLogic = kea([ ], projectedTotalAmountUsdWithBillingLimits: [ (s) => [s.billing], - (billing: BillingV2Type): number => { + (billing: BillingType): number => { if (!billing) { return 0 } @@ -340,7 +340,7 @@ export const billingLogic = kea([ ], supportPlans: [ (s) => [s.billing], - (billing: BillingV2Type): BillingV2PlanType[] => { + (billing: BillingType): BillingPlanType[] => { const platformAndSupportProduct = billing?.products?.find( (product) => product.type == ProductKey.PLATFORM_AND_SUPPORT ) @@ -357,7 +357,7 @@ export const billingLogic = kea([ ], hasSupportAddonPlan: [ (s) => [s.billing], - (billing: BillingV2Type): boolean => { + (billing: BillingType): boolean => { return !!billing?.products ?.find((product) => product.type == ProductKey.PLATFORM_AND_SUPPORT) ?.addons.find((addon) => addon.plans.find((plan) => plan.current_plan)) @@ -394,7 +394,7 @@ export const billingLogic = kea([ }, })), listeners(({ actions, values }) => ({ - reportBillingV2Shown: () => { + reportBillingShown: () => { posthog.capture('billing v2 shown') }, reportBillingAlertShown: ({ alertConfig }) => { diff --git a/frontend/src/scenes/billing/billingProductLogic.ts b/frontend/src/scenes/billing/billingProductLogic.ts index e365ca8fa2e72..2441c100ad43c 100644 --- a/frontend/src/scenes/billing/billingProductLogic.ts +++ b/frontend/src/scenes/billing/billingProductLogic.ts @@ -6,7 +6,7 @@ import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import posthog from 'posthog-js' import React from 'react' -import { BillingProductV2AddonType, BillingProductV2Type, BillingV2PlanType, BillingV2TierType } from '~/types' +import { BillingPlanType, BillingProductV2AddonType, BillingProductV2Type, BillingTierType } from '~/types' import { convertAmountToUsage } from './billing-utils' import { billingLogic } from './billingLogic' @@ -63,7 +63,7 @@ export const billingProductLogic = kea([ setBillingProductLoading: (productKey: string | null) => ({ productKey }), initiateProductUpgrade: ( product: BillingProductV2Type | BillingProductV2AddonType, - plan: BillingV2PlanType, + plan: BillingPlanType, redirectPath?: string ) => ({ plan, @@ -156,7 +156,7 @@ export const billingProductLogic = kea([ currentAndUpgradePlans: [ (_s, p) => [p.product], (product) => { - const currentPlanIndex = product.plans.findIndex((plan: BillingV2PlanType) => plan.current_plan) + const currentPlanIndex = product.plans.findIndex((plan: BillingPlanType) => plan.current_plan) const currentPlan = currentPlanIndex >= 0 ? product.plans?.[currentPlanIndex] : null const upgradePlan = // If in debug mode and with no license there will be @@ -188,9 +188,9 @@ export const billingProductLogic = kea([ const addonTiers = product.addons ?.filter((addon: BillingProductV2AddonType) => addon.subscribed) ?.map((addon: BillingProductV2AddonType) => addon.tiers) - const productAndAddonTiers: BillingV2TierType[][] = [product.tiers, ...addonTiers].filter( + const productAndAddonTiers: BillingTierType[][] = [product.tiers, ...addonTiers].filter( Boolean - ) as BillingV2TierType[][] + ) as BillingTierType[][] return product.tiers ? isEditingBillingLimit ? convertAmountToUsage( @@ -355,9 +355,9 @@ export const billingProductLogic = kea([ ?.map((addon: BillingProductV2AddonType) => addon.tiers) : [] - const productAndAddonTiers: BillingV2TierType[][] = [props.product.tiers, ...addonTiers].filter( + const productAndAddonTiers: BillingTierType[][] = [props.product.tiers, ...addonTiers].filter( Boolean - ) as BillingV2TierType[][] + ) as BillingTierType[][] const newAmountAsUsage = props.product.tiers ? convertAmountToUsage(`${input}`, productAndAddonTiers, values.billing?.discount_percent) diff --git a/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx b/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx index 75924b11061eb..8a410d91471dc 100644 --- a/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx +++ b/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx @@ -12,13 +12,13 @@ import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' import { getProductIcon } from 'scenes/products/Products' import { userLogic } from 'scenes/userLogic' -import { BillingProductV2Type, BillingV2FeatureType, ProductKey } from '~/types' +import { BillingFeatureType, BillingProductV2Type, ProductKey } from '~/types' import { onboardingLogic, OnboardingStepKey } from './onboardingLogic' import { OnboardingStep } from './OnboardingStep' import { multiInstallProducts, sdksLogic } from './sdks/sdksLogic' -export const Feature = ({ name, description, images }: BillingV2FeatureType): JSX.Element => { +export const Feature = ({ name, description, images }: BillingFeatureType): JSX.Element => { return images ? (
  • @@ -32,7 +32,7 @@ export const Feature = ({ name, description, images }: BillingV2FeatureType): JS ) } -export const Subfeature = ({ name, description, icon_key }: BillingV2FeatureType): JSX.Element => { +export const Subfeature = ({ name, description, icon_key }: BillingFeatureType): JSX.Element => { return (
  • {getProductIcon(name, icon_key)} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 4fe44d361ef4f..f62f94ca17980 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -345,7 +345,7 @@ export interface OrganizationType extends OrganizationBasicType { updated_at: string plugins_access_level: PluginsAccessLevel teams: TeamBasicType[] - available_product_features: BillingV2FeatureType[] + available_product_features: BillingFeatureType[] is_member_join_email_enabled: boolean customer_id: string | null enforce_2fa: boolean | null @@ -1490,7 +1490,7 @@ export interface CurrentBillCycleType { current_period_end: number } -export type BillingV2FeatureType = { +export type BillingFeatureType = { key: AvailableFeatureUnion name: string description?: string | null @@ -1506,7 +1506,7 @@ export type BillingV2FeatureType = { type?: 'primary' | 'secondary' | null } -export interface BillingV2TierType { +export interface BillingTierType { flat_amount_usd: string unit_amount_usd: string current_amount_usd: string | null @@ -1529,7 +1529,7 @@ export interface BillingProductV2Type { docs_url: string free_allocation?: number | null subscribed: boolean | null - tiers?: BillingV2TierType[] | null + tiers?: BillingTierType[] | null tiered: boolean current_usage?: number projected_amount_usd?: string | null @@ -1541,10 +1541,10 @@ export interface BillingProductV2Type { has_exceeded_limit: boolean unit: string | null unit_amount_usd: string | null - plans: BillingV2PlanType[] + plans: BillingPlanType[] contact_support: boolean | null inclusion_only: any - features: BillingV2FeatureType[] + features: BillingFeatureType[] addons: BillingProductV2AddonType[] // addons-only: if this addon is included with the base product and not subscribed individually. for backwards compatibility. included_with_main_product?: boolean @@ -1558,7 +1558,7 @@ export interface BillingProductV2AddonType { icon_key?: string docs_url: string | null type: string - tiers: BillingV2TierType[] | null + tiers: BillingTierType[] | null tiered: boolean subscribed: boolean // sometimes addons are included with the base product, but they aren't subscribed individually @@ -1571,15 +1571,15 @@ export interface BillingProductV2AddonType { current_usage: number projected_usage: number | null projected_amount_usd: string | null - plans: BillingV2PlanType[] + plans: BillingPlanType[] usage_key?: string free_allocation?: number | null percentage_usage?: number - features: BillingV2FeatureType[] + features: BillingFeatureType[] included_if?: 'no_active_subscription' | 'has_subscription' | null usage_limit?: number | null } -export interface BillingV2Type { +export interface BillingType { customer_id: string has_active_subscription: boolean subscription_level: 'free' | 'paid' | 'custom' @@ -1601,15 +1601,15 @@ export interface BillingV2Type { license?: { plan: LicensePlan } - available_plans?: BillingV2PlanType[] + available_plans?: BillingPlanType[] discount_percent?: number discount_amount_usd?: string amount_off_expires_at?: Dayjs } -export interface BillingV2PlanType { +export interface BillingPlanType { free_allocation?: number | null - features: BillingV2FeatureType[] + features: BillingFeatureType[] name: string description: string is_free?: boolean @@ -1621,7 +1621,7 @@ export interface BillingV2PlanType { flat_rate: boolean product_key: ProductKeyUnion current_plan?: boolean | null - tiers?: BillingV2TierType[] | null + tiers?: BillingTierType[] | null unit_amount_usd: string | null included_if?: 'no_active_subscription' | 'has_subscription' | null initial_billing_limit?: number