diff --git a/cypress/e2e/billingUpgradeCTA.cy.ts b/cypress/e2e/billingUpgradeCTA.cy.ts
new file mode 100644
index 0000000000000..be4a0a9a03e44
--- /dev/null
+++ b/cypress/e2e/billingUpgradeCTA.cy.ts
@@ -0,0 +1,57 @@
+import { decideResponse } from '../fixtures/api/decide'
+import * as fflate from 'fflate'
+
+// Mainly testing to make sure events are fired as expected
+
+describe('Billing Upgrade CTA', () => {
+ beforeEach(() => {
+ cy.intercept('**/decide/*', (req) =>
+ req.reply(
+ decideResponse({
+ 'billing-upgrade-language': 'credit_card',
+ })
+ )
+ )
+
+ cy.intercept('/api/billing-v2/', { fixture: 'api/billing-v2/billing-v2-unsubscribed.json' })
+ })
+
+ it('Check that events are being sent on each page visit', () => {
+ cy.visit('/organization/billing')
+ cy.get('[data-attr=product_analytics-upgrade-cta] .LemonButton__content').should('have.text', 'Add credit card')
+ cy.window().then((win) => {
+ const events = (win as any)._cypress_posthog_captures
+
+ const matchingEvents = events.filter((event) => event.event === 'billing CTA shown')
+ // One for each product card
+ expect(matchingEvents.length).to.equal(4)
+ })
+
+ // Mock billing response with subscription
+ cy.intercept('/api/billing-v2/', { fixture: 'api/billing-v2/billing-v2.json' })
+ cy.reload()
+
+ cy.get('[data-attr=session_replay-upgrade-cta] .LemonButton__content').should('have.text', 'Add paid plan')
+ cy.intercept('POST', '**/e/?compression=gzip-js*').as('capture3')
+ cy.window().then((win) => {
+ const events = (win as any)._cypress_posthog_captures
+
+ const matchingEvents = events.filter((event) => event.event === 'billing CTA shown')
+ expect(matchingEvents.length).to.equal(4)
+ })
+
+ cy.intercept('/api/billing-v2/', { fixture: 'api/billing-v2/billing-v2-unsubscribed.json' })
+ // Navigate to the onboarding billing step
+ cy.visit('/products')
+ cy.get('[data-attr=product_analytics-onboarding-card]').click()
+ cy.get('[data-attr=onboarding-breadcrumbs] > :nth-child(5)').click()
+
+ cy.intercept('POST', '**/e/?compression=gzip-js*').as('capture4')
+ cy.window().then((win) => {
+ const events = (win as any)._cypress_posthog_captures
+
+ const matchingEvents = events.filter((event) => event.event === 'billing CTA shown')
+ expect(matchingEvents.length).to.equal(3)
+ })
+ })
+})
diff --git a/cypress/e2e/events.cy.ts b/cypress/e2e/events.cy.ts
index d8fea2656987c..fab6627b467d0 100644
--- a/cypress/e2e/events.cy.ts
+++ b/cypress/e2e/events.cy.ts
@@ -72,7 +72,7 @@ describe('Events', () => {
cy.get('[data-attr="new-prop-filter-EventPropertyFilters.0"]').click()
cy.get('[data-attr=taxonomic-filter-searchfield]').click()
cy.get('[data-attr=prop-filter-event_properties-0]').click()
- cy.get('[data-attr=prop-val] .ant-select-selector').click({ force: true })
+ cy.get('[data-attr=prop-val] .LemonInput').click({ force: true })
cy.wait('@getBrowserValues').then(() => {
cy.get('[data-attr=prop-val-0]').click()
cy.get('.DataTable').should('exist')
diff --git a/cypress/e2e/surveys.cy.ts b/cypress/e2e/surveys.cy.ts
index 7d72c850c7ac4..e3efcd4c48a0f 100644
--- a/cypress/e2e/surveys.cy.ts
+++ b/cypress/e2e/surveys.cy.ts
@@ -94,7 +94,7 @@ describe('Surveys', () => {
// select the first property
cy.get('[data-attr="property-select-toggle-0"]').click()
cy.get('[data-attr="prop-filter-person_properties-0"]').click()
- cy.get('[data-attr=prop-val] .ant-select-selector').click({ force: true })
+ cy.get('[data-attr=prop-val] .LemonInput').click({ force: true })
cy.get('[data-attr=prop-val-0]').click({ force: true })
cy.get('[data-attr="rollout-percentage"]').type('100')
diff --git a/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json b/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json
index 60aed5c9693c4..0aec292a9d38d 100644
--- a/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json
+++ b/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json
@@ -47,17 +47,17 @@
"api_access",
"social_sso",
"community_support",
- "terms_and_conditions"
+ "2fa"
],
"license": {
- "plan": "cloud"
+ "plan": "dev"
},
- "customer_id": null,
+ "customer_id": "cus_Pg7PIL8MsKi6bx",
"deactivated": false,
"has_active_subscription": false,
"billing_period": {
- "current_period_start": "2024-02-06T19:37:14.843Z",
- "current_period_end": "2024-03-07T19:37:14.843Z",
+ "current_period_start": "2024-03-04T23:43:35.772Z",
+ "current_period_end": "2024-04-03T23:43:35.772Z",
"interval": "month"
},
"available_product_features": [
@@ -438,12 +438,12 @@
"note": null
},
{
- "key": "terms_and_conditions",
- "name": "Terms and conditions",
- "description": "Terms and conditions",
+ "key": "2fa",
+ "name": "2FA",
+ "description": "Secure your PostHog account with two-factor authentication.",
"unit": null,
"limit": null,
- "note": "Standard"
+ "note": null
}
],
"current_total_amount_usd": null,
@@ -464,7 +464,7 @@
{
"plan_key": "free-20230117",
"product_key": "product_analytics",
- "name": "Product analytics",
+ "name": "Free",
"description": "A comprehensive product analytics platform built to natively work with session replay, feature flags, A/B testing, and surveys.",
"image_url": "https://posthog.com/images/products/product-analytics/product-analytics.png",
"docs_url": "https://posthog.com/docs/product-analytics",
@@ -515,12 +515,14 @@
],
"tiers": null,
"current_plan": true,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
},
{
"plan_key": "paid-20240111",
"product_key": "product_analytics",
- "name": "Product analytics",
+ "name": "Paid",
"description": "A comprehensive product analytics platform built to natively work with session replay, feature flags, A/B testing, and surveys.",
"image_url": "https://posthog.com/images/products/product-analytics/product-analytics.png",
"docs_url": "https://posthog.com/docs/product-analytics",
@@ -576,30 +578,6 @@
"limit": null,
"note": null
},
- {
- "key": "dashboard_permissioning",
- "name": "Dashboard permissions",
- "description": "Restrict access to dashboards within the organization to only those who need it.",
- "unit": null,
- "limit": null,
- "note": null
- },
- {
- "key": "dashboard_collaboration",
- "name": "Tags & text cards",
- "description": "Keep organized by adding tags to your dashboards, cohorts and more. Add text cards and descriptions to your dashboards to provide context to your team.",
- "unit": null,
- "limit": null,
- "note": null
- },
- {
- "key": "ingestion_taxonomy",
- "name": "Ingestion taxonomy",
- "description": "Ingestion taxonomy",
- "unit": null,
- "limit": null,
- "note": null
- },
{
"key": "correlation_analysis",
"name": "Correlation analysis",
@@ -608,14 +586,6 @@
"limit": null,
"note": null
},
- {
- "key": "tagging",
- "name": "Dashboard tags",
- "description": "Organize dashboards with tags.",
- "unit": null,
- "limit": null,
- "note": null
- },
{
"key": "behavioral_cohort_filtering",
"name": "Lifecycle",
@@ -645,7 +615,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000248",
+ "unit_amount_usd": "0.00031",
"up_to": 2000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -654,7 +624,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000104",
+ "unit_amount_usd": "0.00013",
"up_to": 15000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -663,7 +633,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000655",
+ "unit_amount_usd": "0.0000819",
"up_to": 50000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -672,7 +642,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000364",
+ "unit_amount_usd": "0.0000455",
"up_to": 100000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -681,7 +651,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000187",
+ "unit_amount_usd": "0.0000234",
"up_to": 250000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -690,7 +660,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000042",
+ "unit_amount_usd": "0.0000052",
"up_to": null,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -699,7 +669,9 @@
}
],
"current_plan": false,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
}
],
"type": "product_analytics",
@@ -737,7 +709,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000071",
+ "unit_amount_usd": "0.0000708",
"up_to": 2000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -803,7 +775,7 @@
{
"plan_key": "addon-20230509",
"product_key": "group_analytics",
- "name": "Group analytics",
+ "name": "Addon",
"description": "Associate events with a group or entity - such as a company, community, or project. Analyze these events as if they were sent by that entity itself. Great for B2B, marketplaces, and more.",
"image_url": "https://posthog.com/images/product/product-icons/group-analytics.svg",
"docs_url": "https://posthog.com/docs/product-analytics/group-analytics",
@@ -832,7 +804,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000071",
+ "unit_amount_usd": "0.0000708",
"up_to": 2000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -886,10 +858,12 @@
}
],
"current_plan": false,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
}
],
- "contact_support": false
+ "contact_support": null
},
{
"name": "Data pipelines",
@@ -911,7 +885,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000062",
+ "unit_amount_usd": "0.000248",
"up_to": 2000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -920,7 +894,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000026",
+ "unit_amount_usd": "0.000104",
"up_to": 15000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -929,7 +903,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000164",
+ "unit_amount_usd": "0.0000655",
"up_to": 50000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -938,7 +912,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000091",
+ "unit_amount_usd": "0.0000364",
"up_to": 100000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -947,7 +921,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000047",
+ "unit_amount_usd": "0.0000187",
"up_to": 250000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -956,7 +930,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000001",
+ "unit_amount_usd": "0.0000042",
"up_to": null,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -977,7 +951,7 @@
{
"plan_key": "addon-20240111",
"product_key": "data_pipelines",
- "name": "Data pipelines",
+ "name": "Addon",
"description": "Get your PostHog data into your data warehouse or other tools like BigQuery, Redshift, Customer.io, and more.",
"image_url": null,
"docs_url": "https://posthog.com/docs/cdp/batch-exports",
@@ -1006,7 +980,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000062",
+ "unit_amount_usd": "0.000248",
"up_to": 2000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -1015,7 +989,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000026",
+ "unit_amount_usd": "0.000104",
"up_to": 15000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -1024,7 +998,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000164",
+ "unit_amount_usd": "0.0000655",
"up_to": 50000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -1033,7 +1007,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000091",
+ "unit_amount_usd": "0.0000364",
"up_to": 100000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -1042,7 +1016,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.0000047",
+ "unit_amount_usd": "0.0000187",
"up_to": 250000000,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -1051,7 +1025,7 @@
},
{
"flat_amount_usd": "0",
- "unit_amount_usd": "0.000001",
+ "unit_amount_usd": "0.0000042",
"up_to": null,
"current_amount_usd": "0.00",
"current_usage": 0,
@@ -1060,10 +1034,12 @@
}
],
"current_plan": false,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
}
],
- "contact_support": false
+ "contact_support": null
}
],
"contact_support": false,
@@ -1131,30 +1107,6 @@
"icon_key": "IconNotification",
"type": "secondary"
},
- {
- "key": "dashboard_collaboration",
- "name": "Tags & text cards",
- "description": "Keep organized by adding tags to your dashboards, cohorts and more. Add text cards and descriptions to your dashboards to provide context to your team.",
- "images": null,
- "icon_key": null,
- "type": null
- },
- {
- "key": "dashboard_permissioning",
- "name": "Dashboard permissions",
- "description": "Restrict access to dashboards within the organization to only those who need it.",
- "images": null,
- "icon_key": null,
- "type": null
- },
- {
- "key": "ingestion_taxonomy",
- "name": "Ingestion taxonomy",
- "description": "Ingestion taxonomy",
- "images": null,
- "icon_key": null,
- "type": null
- },
{
"key": "paths_advanced",
"name": "Advanced paths",
@@ -1174,14 +1126,6 @@
"icon_key": null,
"type": "primary"
},
- {
- "key": "tagging",
- "name": "Dashboard tags",
- "description": "Organize dashboards with tags.",
- "images": null,
- "icon_key": null,
- "type": null
- },
{
"key": "behavioral_cohort_filtering",
"name": "Lifecycle",
@@ -1256,7 +1200,7 @@
{
"plan_key": "free-20231218",
"product_key": "session_replay",
- "name": "Session replay",
+ "name": "Free",
"description": "Session replay helps you diagnose issues and understand user behavior in your product or website.",
"image_url": "https://posthog.com/images/products/session-replay/session-replay.png",
"docs_url": "https://posthog.com/docs/session-replay",
@@ -1379,12 +1323,14 @@
],
"tiers": null,
"current_plan": true,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
},
{
"plan_key": "paid-20231218",
"product_key": "session_replay",
- "name": "Session replay",
+ "name": "Paid",
"description": "Session replay helps you diagnose issues and understand user behavior in your product or website.",
"image_url": "https://posthog.com/images/products/session-replay/session-replay.png",
"docs_url": "https://posthog.com/docs/session-replay",
@@ -1570,7 +1516,9 @@
}
],
"current_plan": false,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
}
],
"type": "session_replay",
@@ -1753,7 +1701,7 @@
{
"plan_key": "free-20230117",
"product_key": "feature_flags",
- "name": "Feature flags & A/B testing",
+ "name": "Free",
"description": "Test changes with small groups of users before rolling out wider. Analyze usage with product analytics and session replay.",
"image_url": "https://posthog.com/images/products/feature-flags/feature-flags.png",
"docs_url": "https://posthog.com/docs/feature-flags",
@@ -1836,12 +1784,14 @@
],
"tiers": null,
"current_plan": true,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
},
{
"plan_key": "paid-20230623",
"product_key": "feature_flags",
- "name": "Feature flags & A/B testing",
+ "name": "Paid",
"description": "Test changes with small groups of users before rolling out wider. Analyze usage with product analytics and session replay.",
"image_url": "https://posthog.com/images/products/feature-flags/feature-flags.png",
"docs_url": "https://posthog.com/docs/feature-flags",
@@ -2018,7 +1968,9 @@
}
],
"current_plan": false,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
}
],
"type": "feature_flags",
@@ -2215,7 +2167,7 @@
{
"plan_key": "free-20230928",
"product_key": "surveys",
- "name": "Surveys",
+ "name": "Free",
"description": "Build in-app popups with freeform text responses, multiple choice, NPS, ratings, and emoji reactions. Or use the API for complete control.",
"image_url": "https://posthog.com/images/products/surveys/surveys.png",
"docs_url": "https://posthog.com/docs/surveys",
@@ -2290,12 +2242,14 @@
],
"tiers": null,
"current_plan": true,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
},
{
"plan_key": "paid-20230928",
"product_key": "surveys",
- "name": "Surveys",
+ "name": "Paid",
"description": "Build in-app popups with freeform text responses, multiple choice, NPS, ratings, and emoji reactions. Or use the API for complete control.",
"image_url": "https://posthog.com/images/products/surveys/surveys.png",
"docs_url": "https://posthog.com/docs/surveys",
@@ -2449,7 +2403,9 @@
}
],
"current_plan": false,
- "included_if": null
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": null
}
],
"type": "surveys",
@@ -2617,7 +2573,7 @@
{
"plan_key": "free-20230117",
"product_key": "integrations",
- "name": "Integrations",
+ "name": "Free",
"description": "Connect PostHog to your favorite tools.",
"image_url": "https://posthog.com/images/product/product-icons/integrations.svg",
"docs_url": "https://posthog.com/docs/apps",
@@ -2668,12 +2624,14 @@
],
"tiers": null,
"current_plan": true,
- "included_if": "no_active_subscription"
+ "included_if": "no_active_subscription",
+ "contact_support": null,
+ "unit_amount_usd": null
},
{
"plan_key": "paid-20230117",
"product_key": "integrations",
- "name": "Integrations",
+ "name": "Paid",
"description": "Connect PostHog to your favorite tools.",
"image_url": "https://posthog.com/images/product/product-icons/integrations.svg",
"docs_url": "https://posthog.com/docs/apps",
@@ -2732,7 +2690,9 @@
],
"tiers": null,
"current_plan": false,
- "included_if": "has_subscription"
+ "included_if": "has_subscription",
+ "contact_support": null,
+ "unit_amount_usd": null
}
],
"type": "integrations",
@@ -2818,7 +2778,7 @@
{
"plan_key": "free-20230117",
"product_key": "platform_and_support",
- "name": "Platform and support",
+ "name": "Totally free",
"description": "SSO, permission management, and support.",
"image_url": "https://posthog.com/images/product/product-icons/platform.svg",
"docs_url": "https://posthog.com/docs",
@@ -2875,22 +2835,114 @@
"note": null
},
{
- "key": "terms_and_conditions",
- "name": "Terms and conditions",
- "description": "Terms and conditions",
+ "key": "2fa",
+ "name": "2FA",
+ "description": "Secure your PostHog account with two-factor authentication.",
"unit": null,
"limit": null,
- "note": "Standard"
+ "note": null
}
],
"tiers": null,
"current_plan": true,
- "included_if": "no_active_subscription"
+ "included_if": "no_active_subscription",
+ "contact_support": null,
+ "unit_amount_usd": null
},
{
- "plan_key": "paid-20230926",
+ "plan_key": "paid-20240208",
"product_key": "platform_and_support",
- "name": "Platform and support",
+ "name": "With subscription",
+ "description": "SSO, permission management, and support.",
+ "image_url": "https://posthog.com/images/product/product-icons/platform.svg",
+ "docs_url": "https://posthog.com/docs",
+ "note": null,
+ "unit": null,
+ "free_allocation": null,
+ "features": [
+ {
+ "key": "tracked_users",
+ "name": "Tracked users",
+ "description": "Track users across devices and sessions.",
+ "unit": null,
+ "limit": null,
+ "note": "Unlimited"
+ },
+ {
+ "key": "team_members",
+ "name": "Team members",
+ "description": "PostHog doesn't charge per seat add your entire team!",
+ "unit": null,
+ "limit": null,
+ "note": "Unlimited"
+ },
+ {
+ "key": "organizations_projects",
+ "name": "Projects",
+ "description": "Create silos of data within PostHog. All data belongs to a single project and all queries are project-specific.",
+ "unit": "projects",
+ "limit": 2,
+ "note": null
+ },
+ {
+ "key": "api_access",
+ "name": "API access",
+ "description": "Access your data via our developer-friendly API.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "social_sso",
+ "name": "SSO via Google, Github, or Gitlab",
+ "description": "Log in to PostHog with your Google, Github, or Gitlab account.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "community_support",
+ "name": "Community support",
+ "description": "Get help from other users and PostHog team members in our Community forums.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "dedicated_support",
+ "name": "Dedicated account manager",
+ "description": "Work with a dedicated account manager via Slack or email to help you get the most out of PostHog.",
+ "unit": null,
+ "limit": null,
+ "note": "$2k+/month spend"
+ },
+ {
+ "key": "email_support",
+ "name": "Email support",
+ "description": "Get help directly from our product engineers via email. No wading through multiple support people before you get help.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "2fa",
+ "name": "2FA",
+ "description": "Secure your PostHog account with two-factor authentication.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ }
+ ],
+ "tiers": null,
+ "current_plan": false,
+ "included_if": "has_subscription",
+ "contact_support": null,
+ "unit_amount_usd": null
+ },
+ {
+ "plan_key": "teams-20240208",
+ "product_key": "platform_and_support",
+ "name": "Teams",
"description": "SSO, permission management, and support.",
"image_url": "https://posthog.com/images/product/product-icons/platform.svg",
"docs_url": "https://posthog.com/docs",
@@ -2938,6 +2990,70 @@
"limit": null,
"note": null
},
+ {
+ "key": "sso_enforcement",
+ "name": "Enforce SSO login",
+ "description": "Users can only sign up and log in to your PostHog organization with your specified SSO provider.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "2fa",
+ "name": "2FA",
+ "description": "Secure your PostHog account with two-factor authentication.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "2fa_enforcement",
+ "name": "Enforce 2FA",
+ "description": "Require all users in your organization to enable two-factor authentication.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "community_support",
+ "name": "Community support",
+ "description": "Get help from other users and PostHog team members in our Community forums.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "email_support",
+ "name": "Email support",
+ "description": "Get help directly from our product engineers via email. No wading through multiple support people before you get help.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "dedicated_support",
+ "name": "Dedicated account manager",
+ "description": "Work with a dedicated account manager via Slack or email to help you get the most out of PostHog.",
+ "unit": null,
+ "limit": null,
+ "note": "$2k+/month spend"
+ },
+ {
+ "key": "priority_support",
+ "name": "Priority support",
+ "description": "Get help from our team faster than other customers.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "white_labelling",
+ "name": "White labeling",
+ "description": "Use your own branding on surveys, shared dashboards, shared insights, and more.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
{
"key": "project_based_permissioning",
"name": "Project permissions",
@@ -2946,10 +3062,228 @@
"limit": null,
"note": null
},
+ {
+ "key": "advanced_permissions",
+ "name": "Advanced permissions",
+ "description": "Control who can access and modify data and features within your organization.",
+ "unit": null,
+ "limit": null,
+ "note": "Project-based only"
+ },
+ {
+ "key": "audit_logs",
+ "name": "Audit logs",
+ "description": "See who in your organization has accessed or modified entities within PostHog.",
+ "unit": null,
+ "limit": null,
+ "note": "Basic"
+ },
+ {
+ "key": "security_assessment",
+ "name": "Security assessment",
+ "description": "Security assessment",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "hipaa_baa",
+ "name": "HIPAA BAA",
+ "description": "Get a signed HIPAA Business Associate Agreement (BAA) to use PostHog in a HIPAA-compliant manner.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "team_collaboration",
+ "name": "Team collaboration features",
+ "description": "Work together better with tags on dashboards and insights; descriptions on insights, events, & properties; verified events; comments on almost anything.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "ingestion_taxonomy",
+ "name": "Ingestion taxonomy",
+ "description": "Mark events as verified or unverified to help you understand the quality of your data.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "tagging",
+ "name": "Dashboard tags",
+ "description": "Organize dashboards with tags.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ }
+ ],
+ "tiers": [],
+ "current_plan": false,
+ "included_if": null,
+ "contact_support": null,
+ "unit_amount_usd": "450.00"
+ },
+ {
+ "plan_key": "enterprise-20240208",
+ "product_key": "platform_and_support",
+ "name": "Enterprise",
+ "description": "SSO, permission management, and support.",
+ "image_url": "https://posthog.com/images/product/product-icons/platform.svg",
+ "docs_url": "https://posthog.com/docs",
+ "note": null,
+ "unit": null,
+ "free_allocation": null,
+ "features": [
+ {
+ "key": "team_members",
+ "name": "Team members",
+ "description": "PostHog doesn't charge per seat add your entire team!",
+ "unit": null,
+ "limit": null,
+ "note": "Unlimited"
+ },
+ {
+ "key": "organizations_projects",
+ "name": "Projects",
+ "description": "Create silos of data within PostHog. All data belongs to a single project and all queries are project-specific.",
+ "unit": null,
+ "limit": null,
+ "note": "Unlimited"
+ },
+ {
+ "key": "tracked_users",
+ "name": "Tracked users",
+ "description": "Track users across devices and sessions.",
+ "unit": null,
+ "limit": null,
+ "note": "Unlimited"
+ },
+ {
+ "key": "api_access",
+ "name": "API access",
+ "description": "Access your data via our developer-friendly API.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
{
"key": "white_labelling",
"name": "White labeling",
- "description": "Use your own branding in your PostHog organization.",
+ "description": "Use your own branding on surveys, shared dashboards, shared insights, and more.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "team_collaboration",
+ "name": "Team collaboration features",
+ "description": "Work together better with tags on dashboards and insights; descriptions on insights, events, & properties; verified events; comments on almost anything.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "ingestion_taxonomy",
+ "name": "Ingestion taxonomy",
+ "description": "Mark events as verified or unverified to help you understand the quality of your data.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "tagging",
+ "name": "Dashboard tags",
+ "description": "Organize dashboards with tags.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "social_sso",
+ "name": "SSO via Google, Github, or Gitlab",
+ "description": "Log in to PostHog with your Google, Github, or Gitlab account.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "sso_enforcement",
+ "name": "Enforce SSO login",
+ "description": "Users can only sign up and log in to your PostHog organization with your specified SSO provider.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "saml",
+ "name": "SAML SSO",
+ "description": "Allow your organization's users to log in with SAML.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "2fa",
+ "name": "2FA",
+ "description": "Secure your PostHog account with two-factor authentication.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "2fa_enforcement",
+ "name": "Enforce 2FA",
+ "description": "Require all users in your organization to enable two-factor authentication.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "project_based_permissioning",
+ "name": "Project permissions",
+ "description": "Restrict access to data within the organization to only those who need it.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "role_based_access",
+ "name": "Role-based access",
+ "description": "Control access to features like experiments, session recordings, and feature flags with custom roles.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "advanced_permissions",
+ "name": "Advanced permissions",
+ "description": "Control who can access and modify data and features within your organization.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "audit_logs",
+ "name": "Audit logs",
+ "description": "See who in your organization has accessed or modified entities within PostHog.",
+ "unit": null,
+ "limit": null,
+ "note": "Advanced"
+ },
+ {
+ "key": "hipaa_baa",
+ "name": "HIPAA BAA",
+ "description": "Get a signed HIPAA Business Associate Agreement (BAA) to use PostHog in a HIPAA-compliant manner.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "custom_msa",
+ "name": "Custom MSA",
+ "description": "Get a custom Master Services Agreement (MSA) to use PostHog in a way that fits your company's needs.",
"unit": null,
"limit": null,
"note": null
@@ -2963,28 +3297,28 @@
"note": null
},
{
- "key": "dedicated_support",
- "name": "Slack (dedicated channel)",
- "description": "Get help directly from our support team in a dedicated Slack channel shared between you and the PostHog team.",
+ "key": "email_support",
+ "name": "Email support",
+ "description": "Get help directly from our product engineers via email. No wading through multiple support people before you get help.",
"unit": null,
"limit": null,
- "note": "$2k/month spend or above"
+ "note": null
},
{
- "key": "email_support",
- "name": "Direct access to engineers",
- "description": "Get help directly from our product engineers via email. No wading through multiple support people before you get help.",
+ "key": "dedicated_support",
+ "name": "Dedicated account manager",
+ "description": "Work with a dedicated account manager via Slack or email to help you get the most out of PostHog.",
"unit": null,
"limit": null,
"note": null
},
{
- "key": "terms_and_conditions",
- "name": "Terms and conditions",
- "description": "Terms and conditions",
+ "key": "priority_support",
+ "name": "Priority support",
+ "description": "Get help from our team faster than other customers.",
"unit": null,
"limit": null,
- "note": "Standard"
+ "note": null
},
{
"key": "security_assessment",
@@ -2993,11 +3327,29 @@
"unit": null,
"limit": null,
"note": null
+ },
+ {
+ "key": "training",
+ "name": "Ongoing training",
+ "description": "Get training from our team to help you quickly get up and running with PostHog.",
+ "unit": null,
+ "limit": null,
+ "note": null
+ },
+ {
+ "key": "configuration_support",
+ "name": "Personalized onboarding",
+ "description": "Get help from our team to create dashboards that will help you understand your data and your business.",
+ "unit": null,
+ "limit": null,
+ "note": null
}
],
"tiers": null,
"current_plan": false,
- "included_if": "has_subscription"
+ "included_if": null,
+ "contact_support": true,
+ "unit_amount_usd": null
}
],
"type": "platform_and_support",
@@ -3015,7 +3367,7 @@
"projected_amount_usd": null,
"unit": null,
"addons": [],
- "contact_support": true,
+ "contact_support": false,
"inclusion_only": true,
"features": [
{
@@ -3051,17 +3403,17 @@
"type": null
},
{
- "key": "role_based_access",
- "name": "Role-based access",
- "description": "Control access to features like experiments, session recordings, and feature flags with custom roles.",
+ "key": "social_sso",
+ "name": "SSO via Google, Github, or Gitlab",
+ "description": "Log in to PostHog with your Google, Github, or Gitlab account.",
"images": null,
"icon_key": null,
"type": null
},
{
- "key": "social_sso",
- "name": "SSO via Google, Github, or Gitlab",
- "description": "Log in to PostHog with your Google, Github, or Gitlab account.",
+ "key": "role_based_access",
+ "name": "Role-based access",
+ "description": "Control access to features like experiments, session recordings, and feature flags with custom roles.",
"images": null,
"icon_key": null,
"type": null
@@ -3074,6 +3426,14 @@
"icon_key": null,
"type": null
},
+ {
+ "key": "advanced_permissions",
+ "name": "Advanced permissions",
+ "description": "Control who can access and modify data and features within your organization.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
{
"key": "saml",
"name": "SAML SSO",
@@ -3090,10 +3450,26 @@
"icon_key": null,
"type": null
},
+ {
+ "key": "2fa",
+ "name": "2FA",
+ "description": "Secure your PostHog account with two-factor authentication.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
+ {
+ "key": "2fa_enforcement",
+ "name": "Enforce 2FA",
+ "description": "Require all users in your organization to enable two-factor authentication.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
{
"key": "white_labelling",
"name": "White labeling",
- "description": "Use your own branding in your PostHog organization.",
+ "description": "Use your own branding on surveys, shared dashboards, shared insights, and more.",
"images": null,
"icon_key": null,
"type": null
@@ -3108,31 +3484,31 @@
},
{
"key": "dedicated_support",
- "name": "Slack (dedicated channel)",
- "description": "Get help directly from our support team in a dedicated Slack channel shared between you and the PostHog team.",
+ "name": "Dedicated account manager",
+ "description": "Work with a dedicated account manager via Slack or email to help you get the most out of PostHog.",
"images": null,
"icon_key": null,
"type": null
},
{
"key": "email_support",
- "name": "Direct access to engineers",
+ "name": "Email support",
"description": "Get help directly from our product engineers via email. No wading through multiple support people before you get help.",
"images": null,
"icon_key": null,
"type": null
},
{
- "key": "account_manager",
- "name": "Account manager",
- "description": "Work with a dedicated account manager to help you get the most out of PostHog.",
+ "key": "priority_support",
+ "name": "Priority support",
+ "description": "Get help from our team faster than other customers.",
"images": null,
"icon_key": null,
"type": null
},
{
"key": "training",
- "name": "Training sessions",
+ "name": "Ongoing training",
"description": "Get training from our team to help you quickly get up and running with PostHog.",
"images": null,
"icon_key": null,
@@ -3140,7 +3516,7 @@
},
{
"key": "configuration_support",
- "name": "Dashboard configuration support",
+ "name": "Personalized onboarding",
"description": "Get help from our team to create dashboards that will help you understand your data and your business.",
"images": null,
"icon_key": null,
@@ -3185,6 +3561,54 @@
"images": null,
"icon_key": null,
"type": null
+ },
+ {
+ "key": "audit_logs",
+ "name": "Audit logs",
+ "description": "See who in your organization has accessed or modified entities within PostHog.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
+ {
+ "key": "hipaa_baa",
+ "name": "HIPAA BAA",
+ "description": "Get a signed HIPAA Business Associate Agreement (BAA) to use PostHog in a HIPAA-compliant manner.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
+ {
+ "key": "custom_msa",
+ "name": "Custom MSA",
+ "description": "Get a custom Master Services Agreement (MSA) to use PostHog in a way that fits your company's needs.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
+ {
+ "key": "team_collaboration",
+ "name": "Team collaboration features",
+ "description": "Work together better with tags on dashboards and insights; descriptions on insights, events, & properties; verified events; comments on almost anything.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
+ {
+ "key": "ingestion_taxonomy",
+ "name": "Ingestion taxonomy",
+ "description": "Mark events as verified or unverified to help you understand the quality of your data.",
+ "images": null,
+ "icon_key": null,
+ "type": null
+ },
+ {
+ "key": "tagging",
+ "name": "Dashboard tags",
+ "description": "Organize dashboards with tags.",
+ "images": null,
+ "icon_key": null,
+ "type": null
}
]
}
@@ -3213,5 +3637,5 @@
"discount_amount_usd": null,
"amount_off_expires_at": null,
"never_drop_data": null,
- "stripe_portal_url": null
+ "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9QZzdwUjRPMXBobnRsdHdKaDVpbEVzbkREcE9RQnFT0100S0djyn8G"
}
diff --git a/cypress/productAnalytics/index.ts b/cypress/productAnalytics/index.ts
index 00b9279410e1b..6fd8a5c521de5 100644
--- a/cypress/productAnalytics/index.ts
+++ b/cypress/productAnalytics/index.ts
@@ -215,14 +215,6 @@ export const dashboard = {
cy.get('[data-attr="prop-val-0"]').click({ force: true })
cy.get('.PropertyFilterButton').should('have.length', 1)
},
- addPropertyFilter(type: string = 'Browser', value: string = 'Chrome'): void {
- cy.get('.PropertyFilterButton').should('have.length', 0)
- cy.get('[data-attr="property-filter-0"]').click()
- cy.get('[data-attr="taxonomic-filter-searchfield"]').click().type('Browser').wait(1000)
- cy.get('[data-attr="prop-filter-event_properties-0"]').click({ force: true })
- cy.get('.ant-select-selector').type(value)
- cy.get('.ant-select-item-option-content').click({ force: true })
- },
}
export function createInsight(insightName: string): void {
diff --git a/docker-compose.base.yml b/docker-compose.base.yml
index 2e5ce0c2bdb9a..073af7c1d04bb 100644
--- a/docker-compose.base.yml
+++ b/docker-compose.base.yml
@@ -103,7 +103,6 @@ services:
KAFKA_HOSTS: 'kafka:9092'
REDIS_URL: 'redis://redis:6379/'
-
plugins:
command: ./bin/plugin-server --no-restart-loop
restart: on-failure
@@ -152,8 +151,6 @@ services:
volumes:
- /var/lib/elasticsearch/data
temporal:
-
-
environment:
- DB=postgresql
- DB_PORT=5432
@@ -190,4 +187,3 @@ services:
restart: on-failure
environment:
TEMPORAL_HOST: temporal
-
diff --git a/docker-compose.dev-full.yml b/docker-compose.dev-full.yml
index ba940322fb3dd..b8dbe9ebd3c7e 100644
--- a/docker-compose.dev-full.yml
+++ b/docker-compose.dev-full.yml
@@ -182,4 +182,4 @@ services:
- clickhouse
- kafka
- object_storage
- - temporal
\ No newline at end of file
+ - temporal
diff --git a/frontend/@posthog/lemon-ui/src/index.ts b/frontend/@posthog/lemon-ui/src/index.ts
index bd2d23a5b74ea..dd674ab2c4e30 100644
--- a/frontend/@posthog/lemon-ui/src/index.ts
+++ b/frontend/@posthog/lemon-ui/src/index.ts
@@ -21,7 +21,7 @@ export * from 'lib/lemon-ui/LemonModal'
export * from 'lib/lemon-ui/LemonRow'
export * from 'lib/lemon-ui/LemonSegmentedButton'
export * from 'lib/lemon-ui/LemonSelect'
-export * from 'lib/lemon-ui/LemonSelectMultiple'
+export * from 'lib/lemon-ui/LemonInputSelect'
export * from 'lib/lemon-ui/LemonSkeleton'
export * from 'lib/lemon-ui/LemonSnack'
export * from 'lib/lemon-ui/LemonSwitch'
diff --git a/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--dark.png b/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--dark.png
index f10afc853072e..f97d12fe86644 100644
Binary files a/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--dark.png and b/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--dark.png differ
diff --git a/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--light.png b/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--light.png
index 61b90277049e8..418f247258351 100644
Binary files a/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--light.png and b/frontend/__snapshots__/filters-cohort-filters-fields-person-properties--basic--light.png differ
diff --git a/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--dark.png b/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--dark.png
index 6be95c84a2b94..78d03d3f2878f 100644
Binary files a/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--dark.png and b/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--dark.png differ
diff --git a/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--light.png b/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--light.png
index d56fbb0049291..c85b6df7b3923 100644
Binary files a/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--light.png and b/frontend/__snapshots__/filters-propertyfilters--comparing-property-filters--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-colors--color-palette--dark.png b/frontend/__snapshots__/lemon-ui-colors--color-palette--dark.png
index 9b77208d77294..74150138e8bd3 100644
Binary files a/frontend/__snapshots__/lemon-ui-colors--color-palette--dark.png and b/frontend/__snapshots__/lemon-ui-colors--color-palette--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-colors--color-palette--light.png b/frontend/__snapshots__/lemon-ui-colors--color-palette--light.png
index 48775cfc8bad6..62cf1c2370dad 100644
Binary files a/frontend/__snapshots__/lemon-ui-colors--color-palette--light.png and b/frontend/__snapshots__/lemon-ui-colors--color-palette--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--default--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--default--dark.png
new file mode 100644
index 0000000000000..96cc9e2c65fb9
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--default--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--default--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--default--light.png
new file mode 100644
index 0000000000000..7ff297a125ea3
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--default--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--disabled--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--disabled--dark.png
new file mode 100644
index 0000000000000..109cf0f8db9be
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--disabled--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--disabled--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--disabled--light.png
new file mode 100644
index 0000000000000..9baed9618a6aa
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--disabled--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--loading--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--loading--dark.png
new file mode 100644
index 0000000000000..004699f0e3761
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--loading--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--loading--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--loading--light.png
new file mode 100644
index 0000000000000..5bf9eb654b451
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--loading--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select--dark.png
new file mode 100644
index 0000000000000..dfcba89cef491
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select--light.png
new file mode 100644
index 0000000000000..6f9828363b7d4
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select-with-custom--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select-with-custom--dark.png
new file mode 100644
index 0000000000000..aa1ba92d1702f
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select-with-custom--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select-with-custom--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select-with-custom--light.png
new file mode 100644
index 0000000000000..bce77b83e7476
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--multiple-select-with-custom--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--no-options--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--no-options--dark.png
new file mode 100644
index 0000000000000..19881c003fc27
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--no-options--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--no-options--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--no-options--light.png
new file mode 100644
index 0000000000000..a128f92384aa1
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--no-options--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--prefilled-many-values--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--prefilled-many-values--dark.png
new file mode 100644
index 0000000000000..52f7374e18bb6
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--prefilled-many-values--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--prefilled-many-values--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--prefilled-many-values--light.png
new file mode 100644
index 0000000000000..9014b3540f2f4
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--prefilled-many-values--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--single-option-with-custom--dark.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--single-option-with-custom--dark.png
new file mode 100644
index 0000000000000..349c97229174d
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--single-option-with-custom--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-input-select--single-option-with-custom--light.png b/frontend/__snapshots__/lemon-ui-lemon-input-select--single-option-with-custom--light.png
new file mode 100644
index 0000000000000..9ab0f7424fad8
Binary files /dev/null and b/frontend/__snapshots__/lemon-ui-lemon-input-select--single-option-with-custom--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-slider--basic--dark.png b/frontend/__snapshots__/lemon-ui-lemon-slider--basic--dark.png
index 58a3777e5b1bc..b1200cf4c527d 100644
Binary files a/frontend/__snapshots__/lemon-ui-lemon-slider--basic--dark.png and b/frontend/__snapshots__/lemon-ui-lemon-slider--basic--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-slider--basic--light.png b/frontend/__snapshots__/lemon-ui-lemon-slider--basic--light.png
index 828e365954235..787e8259d52c3 100644
Binary files a/frontend/__snapshots__/lemon-ui-lemon-slider--basic--light.png and b/frontend/__snapshots__/lemon-ui-lemon-slider--basic--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--dark.png b/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--dark.png
index 3b4b55227faac..fcfe5de2715ed 100644
Binary files a/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--dark.png and b/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--light.png b/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--light.png
index 44c5df9cd7035..2ebc9ce11d8dc 100644
Binary files a/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--light.png and b/frontend/__snapshots__/lemon-ui-lemon-tag--breakdown-tag--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--dark.png b/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--dark.png
index 80fd7103af84e..a513619924cf3 100644
Binary files a/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--dark.png and b/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--light.png b/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--light.png
index ca420ed8bdc54..f90ea838ab39e 100644
Binary files a/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--light.png and b/frontend/__snapshots__/lemon-ui-scrollable-shadows--horizontal--light.png differ
diff --git a/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--dark.png b/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--dark.png
index ea2b6db0a77ea..b7de7c83d8f1f 100644
Binary files a/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--dark.png and b/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--dark.png differ
diff --git a/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--light.png b/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--light.png
index da9075401372e..8725eaaeaea92 100644
Binary files a/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--light.png and b/frontend/__snapshots__/lemon-ui-scrollable-shadows--vertical--light.png differ
diff --git a/frontend/__snapshots__/posthog-3000-navigation--navigation-base--dark.png b/frontend/__snapshots__/posthog-3000-navigation--navigation-base--dark.png
index 5ab6b69cf8edc..06adea217229e 100644
Binary files a/frontend/__snapshots__/posthog-3000-navigation--navigation-base--dark.png and b/frontend/__snapshots__/posthog-3000-navigation--navigation-base--dark.png differ
diff --git a/frontend/__snapshots__/posthog-3000-navigation--navigation-base--light.png b/frontend/__snapshots__/posthog-3000-navigation--navigation-base--light.png
index 8252b5fdf1bb7..d1bd6fb608eaa 100644
Binary files a/frontend/__snapshots__/posthog-3000-navigation--navigation-base--light.png and b/frontend/__snapshots__/posthog-3000-navigation--navigation-base--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-batchexports--view-export--dark.png b/frontend/__snapshots__/scenes-app-batchexports--view-export--dark.png
index 0f8b113af43cb..5090a2518e09f 100644
Binary files a/frontend/__snapshots__/scenes-app-batchexports--view-export--dark.png and b/frontend/__snapshots__/scenes-app-batchexports--view-export--dark.png differ
diff --git a/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--dark.png b/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--dark.png
index 06adf374409a7..5c5c43c394a5d 100644
Binary files a/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--dark.png and b/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--dark.png differ
diff --git a/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--light.png b/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--light.png
index c6ae09e9748b5..ee6f207261e02 100644
Binary files a/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--light.png and b/frontend/__snapshots__/scenes-app-notebooks--notebooks-list--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--dark.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--dark.png
new file mode 100644
index 0000000000000..c17dad0b17e61
Binary files /dev/null and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--dark.png differ
diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--light.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--light.png
new file mode 100644
index 0000000000000..474c85c22515e
Binary files /dev/null and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-no-email--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--dark.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--dark.png
new file mode 100644
index 0000000000000..4f968d7f68b6f
Binary files /dev/null and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--dark.png differ
diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--light.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--light.png
new file mode 100644
index 0000000000000..d92a0899a7bd2
Binary files /dev/null and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-support-with-email--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--dark.png b/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--dark.png
index cfb811a0a8e4d..db9932e1f2320 100644
Binary files a/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--dark.png and b/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--dark.png differ
diff --git a/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--light.png b/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--light.png
index cb81bc3c5ac25..bbbe76e720788 100644
Binary files a/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--light.png and b/frontend/__snapshots__/scenes-app-surveys--new-survey-targeting-section--light.png differ
diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--dark.png b/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--dark.png
index f3521aa3d9ba9..d58d47379698a 100644
Binary files a/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--dark.png and b/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--dark.png differ
diff --git a/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--light.png b/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--light.png
index a0bf0be976004..d452707e2f2c4 100644
Binary files a/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--light.png and b/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--light.png differ
diff --git a/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png b/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png
index 243cee06e1f0d..593c08ed4bcec 100644
Binary files a/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png and b/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--dark.png differ
diff --git a/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--light.png b/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--light.png
index dfc8dab84102a..a4a4045de7b70 100644
Binary files a/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--light.png and b/frontend/__snapshots__/scenes-other-onboarding--onboarding-billing--light.png differ
diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png
index aafe7723ef5bc..cb28d08f5b090 100644
Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-project--dark.png differ
diff --git a/frontend/__snapshots__/scenes-other-settings--settings-project--light.png b/frontend/__snapshots__/scenes-other-settings--settings-project--light.png
index 0b458d548f41e..31dbd5b34cfcf 100644
Binary files a/frontend/__snapshots__/scenes-other-settings--settings-project--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-project--light.png differ
diff --git a/frontend/src/layout/navigation-3000/components/AlgoliaSearch.tsx b/frontend/src/layout/navigation-3000/components/AlgoliaSearch.tsx
new file mode 100644
index 0000000000000..b6c52e3018133
--- /dev/null
+++ b/frontend/src/layout/navigation-3000/components/AlgoliaSearch.tsx
@@ -0,0 +1,268 @@
+import { IconCheckCircle } from '@posthog/icons'
+import { LemonButton, LemonInput, LemonTag } from '@posthog/lemon-ui'
+import algoliasearch from 'algoliasearch/lite'
+import { useActions } from 'kea'
+import { useEffect, useRef, useState } from 'react'
+import { InstantSearch, useHits, useRefinementList, useSearchBox } from 'react-instantsearch'
+import { AutoSizer } from 'react-virtualized/dist/es/AutoSizer'
+import { List } from 'react-virtualized/dist/es/List'
+
+import { sidePanelStateLogic } from '~/layout/navigation-3000/sidepanel/sidePanelStateLogic'
+import { SidePanelTab } from '~/types'
+
+const searchClient = algoliasearch('7VNQB5W0TX', '37f41fd37095bc85af76ed4edc85eb5a')
+
+const rowRenderer = ({ key, index, style, hits, activeOption }: any): JSX.Element => {
+ const { slug, title, type, resolved } = hits[index]
+ return (
+ // eslint-disable-next-line react/forbid-dom-props
+
+
+
+
+ {title}
+ {type === 'question' && resolved && (
+
+ )}
+
+ /{slug}
+
+
+
+ )
+}
+
+const Hits = ({ activeOption }: { activeOption?: number }): JSX.Element => {
+ const { hits } = useHits()
+ return (
+
+
+ {({ height, width }: { height: number; width: number }) => (
+ rowRenderer({ ...options, hits, activeOption })}
+ />
+ )}
+
+
+ )
+}
+
+const SearchInput = ({
+ value,
+ setValue,
+}: {
+ value: string
+ setValue: React.Dispatch>
+}): JSX.Element => {
+ const { refine } = useSearchBox()
+
+ const handleChange = (value: string): void => {
+ setValue(value)
+ refine(value)
+ }
+
+ return
+}
+
+type Tag = {
+ type: string
+ label: string
+}
+
+const tags: Tag[] = [
+ {
+ type: 'all',
+ label: 'All',
+ },
+ {
+ type: 'docs',
+ label: 'Docs',
+ },
+ {
+ type: 'question',
+ label: 'Questions',
+ },
+ {
+ type: 'tutorial',
+ label: 'Tutorials',
+ },
+]
+
+type SearchTagProps = Tag & {
+ active?: boolean
+ onClick: (type: string) => void
+}
+
+const SearchTag = ({ type, label, active, onClick }: SearchTagProps): JSX.Element => {
+ const { refine, items } = useRefinementList({ attribute: 'type' })
+ const itemCount = type !== 'all' && items.find(({ value }) => value === type)?.count
+
+ const handleClick = (e: React.MouseEvent): void => {
+ e.stopPropagation()
+ onClick(type)
+ }
+
+ useEffect(() => {
+ refine(type)
+ }, [])
+
+ return (
+
+
+ {label}
+ {type !== 'all' && ({itemCount ?? 0}) }
+
+
+ )
+}
+
+const Tags = ({
+ activeTag,
+ setActiveTag,
+}: {
+ activeTag: string
+ setActiveTag: React.Dispatch>
+}): JSX.Element => {
+ const handleClick = (type: string): void => {
+ setActiveTag(type)
+ }
+
+ return (
+
+ {tags.map((tag) => {
+ const { type } = tag
+ return (
+
+
+
+ )
+ })}
+
+ )
+}
+
+const Search = (): JSX.Element => {
+ const { openSidePanel } = useActions(sidePanelStateLogic)
+ const { hits } = useHits()
+ const { items, refine } = useRefinementList({ attribute: 'type' })
+
+ const ref = useRef(null)
+ const [searchValue, setSearchValue] = useState('')
+ const [activeOption, setActiveOption] = useState()
+ const [activeTag, setActiveTag] = useState('all')
+ const [searchOpen, setSearchOpen] = useState(false)
+
+ const handleKeyDown = (e: React.KeyboardEvent): void => {
+ switch (e.key) {
+ case 'Enter': {
+ if (activeOption !== undefined) {
+ openSidePanel(SidePanelTab.Docs, `https://posthog.com/${hits[activeOption].slug}`)
+ }
+ break
+ }
+
+ case 'Escape': {
+ setSearchOpen(false)
+ break
+ }
+ case 'ArrowDown': {
+ e.preventDefault()
+ setActiveOption((currOption) => {
+ if (currOption === undefined || currOption >= hits.length - 1) {
+ return 0
+ }
+ return currOption + 1
+ })
+ break
+ }
+ case 'ArrowUp': {
+ e.preventDefault()
+ setActiveOption((currOption) => {
+ if (currOption !== undefined) {
+ return currOption <= 0 ? hits.length - 1 : currOption - 1
+ }
+ })
+ break
+ }
+ case 'Tab':
+ case 'ArrowRight': {
+ e.preventDefault()
+ const currTagIndex = tags.findIndex(({ type }) => type === activeTag)
+ setActiveTag(tags[currTagIndex >= tags.length - 1 ? 0 : currTagIndex + 1].type)
+ break
+ }
+ case 'ArrowLeft': {
+ e.preventDefault()
+ const currTagIndex = tags.findIndex(({ type }) => type === activeTag)
+ setActiveTag(tags[currTagIndex <= 0 ? tags.length - 1 : currTagIndex - 1].type)
+ }
+ }
+ }
+
+ useEffect(() => {
+ setSearchOpen(!!searchValue)
+ setActiveOption(0)
+ }, [searchValue])
+
+ useEffect(() => {
+ setActiveOption(0)
+ if (activeTag === 'all') {
+ const filteredItems = items.filter(({ value }) => tags.some(({ type }) => type === value))
+ filteredItems.forEach(({ value, isRefined }) => {
+ if (!isRefined) {
+ refine(value)
+ }
+ })
+ } else {
+ items.forEach(({ value, isRefined }) => {
+ if (isRefined) {
+ refine(value)
+ }
+ })
+ refine(activeTag)
+ }
+ }, [activeTag])
+
+ useEffect(() => {
+ const handleClick = (e: any): void => {
+ if (!ref?.current?.contains(e.target)) {
+ setSearchOpen(false)
+ }
+ }
+
+ window.addEventListener('click', handleClick)
+
+ return () => {
+ window.removeEventListener('click', handleClick)
+ }
+ }, [])
+
+ return (
+
+
+ {searchOpen && (
+
+
+
+
+ )}
+
+ )
+}
+
+export default function AlgoliaSearch(): JSX.Element {
+ return (
+
+
+
+ )
+}
diff --git a/frontend/src/layout/navigation-3000/sidepanel/SidePanel.stories.tsx b/frontend/src/layout/navigation-3000/sidepanel/SidePanel.stories.tsx
index abad5ff308743..2cbd7574fa1fb 100644
--- a/frontend/src/layout/navigation-3000/sidepanel/SidePanel.stories.tsx
+++ b/frontend/src/layout/navigation-3000/sidepanel/SidePanel.stories.tsx
@@ -1,11 +1,13 @@
import { Meta, StoryFn } from '@storybook/react'
import { useActions } from 'kea'
import { router } from 'kea-router'
+import { supportLogic } from 'lib/components/Support/supportLogic'
import { useEffect } from 'react'
import { App } from 'scenes/App'
import { urls } from 'scenes/urls'
-import { mswDecorator } from '~/mocks/browser'
+import { mswDecorator, useStorybookMocks } from '~/mocks/browser'
+import organizationCurrent from '~/mocks/fixtures/api/organizations/@current/@current.json'
import { SidePanelTab } from '~/types'
import { sidePanelStateLogic } from './sidePanelStateLogic'
@@ -59,3 +61,36 @@ export const SidePanelActivation: StoryFn = () => {
export const SidePanelNotebooks: StoryFn = () => {
return
}
+
+export const SidePanelSupportNoEmail: StoryFn = () => {
+ return
+}
+
+export const SidePanelSupportWithEmail: StoryFn = () => {
+ const { openEmailForm } = useActions(supportLogic)
+ useStorybookMocks({
+ get: {
+ // TODO: setting available featues should be a decorator to make this easy
+ '/api/users/@me': () => [
+ 200,
+ {
+ email: 'test@posthog.com',
+ first_name: 'Test Hedgehog',
+ organization: {
+ ...organizationCurrent,
+ available_product_features: [
+ {
+ key: 'email_support',
+ name: 'Email support',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ })
+ useEffect(() => {
+ openEmailForm()
+ }, [])
+ return
+}
diff --git a/frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx b/frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx
index 8b6b61e55faa2..bcad53bdc9dfb 100644
--- a/frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx
+++ b/frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx
@@ -37,7 +37,7 @@ export const SIDE_PANEL_TABS: Record<
noModalSupport: true,
},
[SidePanelTab.Support]: {
- label: 'Support',
+ label: 'Help',
Icon: IconSupport,
Content: SidePanelSupport,
},
diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx
index 381cd59181267..022b8d18dfbbb 100644
--- a/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx
+++ b/frontend/src/layout/navigation-3000/sidepanel/panels/SidePanelSupport.tsx
@@ -1,51 +1,305 @@
-import { LemonButton } from '@posthog/lemon-ui'
+import {
+ IconBug,
+ IconChevronDown,
+ IconFeatures,
+ IconFlask,
+ IconHelmet,
+ IconMap,
+ IconMessage,
+ IconRewindPlay,
+ IconStack,
+ IconToggle,
+ IconTrends,
+} from '@posthog/icons'
+import { LemonButton, Link } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { SupportForm } from 'lib/components/Support/SupportForm'
import { supportLogic } from 'lib/components/Support/supportLogic'
+import React from 'react'
+import { billingLogic } from 'scenes/billing/billingLogic'
+import { urls } from 'scenes/urls'
+import { userLogic } from 'scenes/userLogic'
-import { SidePanelTab } from '~/types'
+import { AvailableFeature, ProductKey, SidePanelTab } from '~/types'
+import AlgoliaSearch from '../../components/AlgoliaSearch'
import { SidePanelPaneHeader } from '../components/SidePanelPaneHeader'
+import { SIDE_PANEL_TABS } from '../SidePanel'
import { sidePanelStateLogic } from '../sidePanelStateLogic'
+const PRODUCTS = [
+ {
+ name: 'Product OS',
+ slug: 'product-os',
+ icon: ,
+ },
+ {
+ name: 'Product analytics',
+ slug: 'product-analytics',
+ icon: ,
+ },
+ {
+ name: 'Session replay',
+ slug: 'session-replay',
+ icon: ,
+ },
+ {
+ name: 'Feature flags',
+ slug: 'feature-flags',
+ icon: ,
+ },
+ {
+ name: 'A/B testing',
+ slug: 'ab-testing',
+ icon: ,
+ },
+ {
+ name: 'Surveys',
+ slug: 'surveys',
+ icon: ,
+ },
+]
+
+const Section = ({ title, children }: { title: string; children: React.ReactNode }): React.ReactElement => {
+ return (
+
+ )
+}
+
+const SupportFormBlock = ({ onCancel }: { onCancel: () => void }): JSX.Element => {
+ const { billing } = useValues(billingLogic)
+ const supportResponseTimes = {
+ [AvailableFeature.EMAIL_SUPPORT]: '2-3 days',
+ [AvailableFeature.PRIORITY_SUPPORT]: '4-6 hours',
+ }
+
+ return (
+
+
+
+
+ Avg support response times
+
+
+ Explore options
+
+
+ {billing?.products
+ ?.find((product) => product.type == ProductKey.PLATFORM_AND_SUPPORT)
+ ?.plans?.map((plan, i) => (
+
+
+ {i == 1 ? 'Pay-per-use' : plan.name}
+ {plan.current_plan && (
+ <>
+ {' '}
+ (your plan)
+ >
+ )}
+
+
+ {plan.features.some((f) => f.key == AvailableFeature.PRIORITY_SUPPORT)
+ ? supportResponseTimes[AvailableFeature.PRIORITY_SUPPORT]
+ : plan.features.some((f) => f.key == AvailableFeature.EMAIL_SUPPORT)
+ ? supportResponseTimes[AvailableFeature.EMAIL_SUPPORT]
+ : 'Community support only'}
+
+
+ ))}
+
+
+
+ Submit
+
+
+ Cancel
+
+
+ )
+}
+
export const SidePanelSupport = (): JSX.Element => {
const { closeSidePanel } = useActions(sidePanelStateLogic)
+ const { hasAvailableFeature } = useValues(userLogic)
+ const { openEmailForm, closeEmailForm } = useActions(supportLogic)
+ const { isEmailFormOpen } = useValues(supportLogic)
const theLogic = supportLogic({ onClose: () => closeSidePanel(SidePanelTab.Support) })
const { title } = useValues(theLogic)
- const { closeSupportForm } = useActions(theLogic)
return (
<>
-
+
-
+
+
+
-
+
+
+
+
+
+ }
+ targetBlank
+ >
+ Report a bug
+
+
+
+ }
+ targetBlank
+ >
+ See what we're building
+
+
+
+ }
+ targetBlank
+ >
+ Vote on our roadmap
+
+
+
+ }
+ targetBlank
+ >
+ Request a feature
+
+
+
+
+
+ {hasAvailableFeature(AvailableFeature.EMAIL_SUPPORT) ? (
+
+ {isEmailFormOpen ? (
+ closeEmailForm()} />
+ ) : (
+
+ Can't find what you need in the docs?{' '}
+ openEmailForm()}>Email an engineer
+
+ )}
+
+ ) : (
+
+
+ Due to our large userbase, we're unable to offer email support to organizations on the
+ free plan. But we still want to help!
+
+
+
+
+ Search our docs
+
+ We're constantly updating our docs and tutorials to provide the latest
+ information about installing, using, and troubleshooting.
+
+
+
+ Ask a community question
+
+ Many common (and niche) questions have already been resolved by users just like
+ you. (Our own engineers also keep an eye on the questions as they have time!){' '}
+
+ Search community questions or ask your own.
+
+
+
+
+
+ Explore PostHog partners
+
+
+ Third-party providers can help with installation and debugging of data issues.
+
+
+
+ Upgrade to a paid plan
+
+ Our paid plans offer email support.{' '}
+
+ Explore options.
+
+
+
+
+
+ )}
>
diff --git a/frontend/src/layout/navigation/ProjectSwitcher.tsx b/frontend/src/layout/navigation/ProjectSwitcher.tsx
index 1fda0cb22f47d..3cd08496f447c 100644
--- a/frontend/src/layout/navigation/ProjectSwitcher.tsx
+++ b/frontend/src/layout/navigation/ProjectSwitcher.tsx
@@ -46,6 +46,7 @@ export function ProjectSwitcherOverlay({ onClickInside }: { onClickInside?: () =
fullWidth
disabled={!!projectCreationForbiddenReason}
tooltip={projectCreationForbiddenReason}
+ data-attr="new-project-button"
onClick={() => {
onClickInside?.()
guardAvailableFeature(
diff --git a/frontend/src/lib/components/BillingUpgradeCTA.tsx b/frontend/src/lib/components/BillingUpgradeCTA.tsx
new file mode 100644
index 0000000000000..cf9278e909af3
--- /dev/null
+++ b/frontend/src/lib/components/BillingUpgradeCTA.tsx
@@ -0,0 +1,13 @@
+import { useActions } from 'kea'
+import { LemonButton, LemonButtonProps } from 'lib/lemon-ui/LemonButton'
+import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
+import { useEffect } from 'react'
+
+export function BillingUpgradeCTA({ children, ...props }: LemonButtonProps): JSX.Element {
+ const { reportBillingCTAShown } = useActions(eventUsageLogic)
+ useEffect(() => {
+ reportBillingCTAShown()
+ }, [])
+
+ return {children}
+}
diff --git a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.scss b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.scss
deleted file mode 100644
index 6bb87aa7f7ec7..0000000000000
--- a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-.property-filters-property-value {
- min-width: 150px;
- min-height: 40px;
- background-color: var(--bg-light);
- border: 1px solid var(--border);
- border-radius: var(--radius);
-
- .ant-select-selection-search,
- .ant-select-selection-placeholder {
- display: flex;
- align-items: center;
- padding: 0 4px !important;
- }
-
- &.ant-select-single {
- .ant-select-selector {
- height: unset;
- min-height: 38px !important;
- background-color: inherit;
- border: none !important;
- }
- }
-
- &.ant-select-multiple {
- .ant-select-selector {
- height: 100% !important;
- padding: 5px 40px 5px 11px;
- background-color: inherit;
- border: none !important;
-
- .ant-select-selection-search {
- padding-left: 0 !important;
- }
-
- .ant-select-selection-placeholder {
- padding-left: 6px !important;
- }
- }
- }
-}
diff --git a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx
index fae22ccf8a00e..8f6cb1e96a68b 100644
--- a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx
+++ b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx
@@ -1,16 +1,12 @@
-import './PropertyValue.scss'
-
-import { AutoComplete } from 'antd'
-import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { DateFilter } from 'lib/components/DateFilter/DateFilter'
import { DurationPicker } from 'lib/components/DurationPicker/DurationPicker'
import { PropertyFilterDatePicker } from 'lib/components/PropertyFilters/components/PropertyFilterDatePicker'
import { propertyFilterTypeToPropertyDefinitionType } from 'lib/components/PropertyFilters/utils'
import { dayjs } from 'lib/dayjs'
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { formatDate, isOperatorDate, isOperatorFlag, isOperatorMulti, toString } from 'lib/utils'
-import { useEffect, useRef, useState } from 'react'
+import { useEffect } from 'react'
import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel'
import { PropertyFilterType, PropertyOperator, PropertyType } from '~/types'
@@ -20,43 +16,26 @@ export interface PropertyValueProps {
type: PropertyFilterType
endpoint?: string // Endpoint to fetch options from
placeholder?: string
- className?: string
onSet: CallableFunction
value?: string | number | Array | null
operator: PropertyOperator
autoFocus?: boolean
- allowCustom?: boolean
eventNames?: string[]
addRelativeDateTimeOptions?: boolean
}
-function matchesLowerCase(needle?: string, haystack?: string): boolean {
- if (typeof haystack !== 'string' || typeof needle !== 'string') {
- return false
- }
- return haystack.toLowerCase().indexOf(needle.toLowerCase()) > -1
-}
-
export function PropertyValue({
propertyKey,
type,
endpoint = undefined,
placeholder = undefined,
- className,
onSet,
value,
operator,
autoFocus = false,
- allowCustom = true,
eventNames = [],
addRelativeDateTimeOptions = false,
}: PropertyValueProps): JSX.Element {
- // what the human has typed into the box
- const [input, setInput] = useState(Array.isArray(value) ? '' : toString(value) ?? '')
-
- const [shouldBlur, setShouldBlur] = useState(false)
- const autoCompleteRef = useRef(null)
-
const { formatPropertyValueForDisplay, describeProperty, options } = useValues(propertyDefinitionsModel)
const { loadPropertyValues } = useActions(propertyDefinitionsModel)
@@ -67,20 +46,6 @@ export function PropertyValue({
const isDurationProperty =
propertyKey && describeProperty(propertyKey, propertyDefinitionType) === PropertyType.Duration
- // update the input field if passed a new `value` prop
- useEffect(() => {
- if (value == null) {
- setInput('')
- } else if (!Array.isArray(value) && toString(value) !== input) {
- const valueObject = options[propertyKey]?.values?.find((v) => v.id === value)
- if (valueObject) {
- setInput(toString(valueObject.name))
- } else {
- setInput(toString(value))
- }
- }
- }, [value])
-
const load = (newInput: string | undefined): void => {
loadPropertyValues({
endpoint,
@@ -91,114 +56,26 @@ export function PropertyValue({
})
}
- function setValue(newValue: PropertyValueProps['value']): void {
- onSet(newValue)
- if (isMultiSelect) {
- setInput('')
- }
- }
+ const setValue = (newValue: PropertyValueProps['value']): void => onSet(newValue)
useEffect(() => {
load('')
}, [propertyKey])
- useEffect(() => {
- if (input === '' && shouldBlur) {
- ;(document.activeElement as HTMLElement)?.blur()
- setShouldBlur(false)
- }
- }, [input, shouldBlur])
-
- const displayOptions = (options[propertyKey]?.values || []).filter(
- (option) => input === '' || matchesLowerCase(input, toString(option?.name))
- )
+ const displayOptions = options[propertyKey]?.values || []
- const commonInputProps = {
- onSearch: (newInput: string) => {
- setInput(newInput)
- if (!Object.keys(options).includes(newInput) && !(operator && isOperatorFlag(operator))) {
- load(newInput.trim())
- }
- },
- ['data-attr']: 'prop-val',
- dropdownMatchSelectWidth: 350,
- placeholder,
- allowClear: Boolean(value),
- onKeyDown: (e: React.KeyboardEvent) => {
- if (e.key === 'Escape') {
- setInput('')
- setShouldBlur(true)
- return
- }
- if (!isMultiSelect && e.key === 'Enter') {
- // We have not explicitly selected a dropdown item by pressing the up/down keys; or the ref is unavailable
- if (
- !autoCompleteRef.current ||
- autoCompleteRef.current?.querySelectorAll?.('.ant-select-item-option-active')?.length === 0
- ) {
- setValue(input)
- }
- }
- },
- handleBlur: () => {
- if (input != '') {
- if (Array.isArray(value) && !value.includes(input)) {
- setValue([...value, ...[input]])
- } else if (!Array.isArray(value)) {
- setValue(input)
- }
- setInput('')
- }
- },
+ const onSearchTextChange = (newInput: string): void => {
+ if (!Object.keys(options).includes(newInput) && !(operator && isOperatorFlag(operator))) {
+ load(newInput.trim())
+ }
}
- if (isMultiSelect) {
- const formattedValues = (
- value === null || value === undefined ? [] : Array.isArray(value) ? value : [value]
- ).map((label) => String(formatPropertyValueForDisplay(propertyKey, label)))
- return (
- {
- setValue(nextVal)
- }}
- onBlur={commonInputProps.handleBlur}
- // TODO: When LemonSelectMultiple is free of AntD, add footnote that pressing comma applies the value
- options={Object.fromEntries([
- ...displayOptions.map(({ name: _name }, index) => {
- const name = toString(_name)
- return [
- name,
- {
- label: name,
- labelComponent: (
-
- {name === '' ? (
- (empty string)
- ) : (
- formatPropertyValueForDisplay(propertyKey, name)
- )}
-
- ),
- },
- ]
- }),
- ])}
- />
- )
+ if (isDurationProperty) {
+ return
}
- if (isDateTimeProperty && addRelativeDateTimeOptions) {
- if (operator === PropertyOperator.IsDateExact) {
+ if (isDateTimeProperty) {
+ if (!addRelativeDateTimeOptions || operator === PropertyOperator.IsDateExact) {
return (
)
@@ -241,52 +118,32 @@ export function PropertyValue({
)
}
- return isDateTimeProperty ? (
-
- ) : isDurationProperty ? (
-
- ) : (
- {
- setInput('')
- setValue('')
- }}
- onChange={(val) => {
- setInput(toString(val))
- }}
- onSelect={(val, option) => {
- setInput(option.title)
- setValue(toString(val).trim())
- }}
- ref={autoCompleteRef}
- >
- {[
- ...(input && allowCustom && !displayOptions.some(({ name }) => input === toString(name))
- ? [
-
- Specify: {input}
- ,
- ]
- : []),
- ...displayOptions.map(({ name: _name, id }, index) => {
- const name = toString(_name)
- return (
-
- {name}
-
- )
- }),
- ]}
-
+ const formattedValues = (value === null || value === undefined ? [] : Array.isArray(value) ? value : [value]).map(
+ (label) => String(formatPropertyValueForDisplay(propertyKey, label))
+ )
+
+ return (
+ setValue(nextVal)}
+ onInputChange={onSearchTextChange}
+ placeholder={placeholder}
+ options={displayOptions.map(({ name: _name }, index) => {
+ const name = toString(_name)
+ return {
+ key: name,
+ label: name,
+ labelComponent: (
+
+ {name === '' ? (empty string) : formatPropertyValueForDisplay(propertyKey, name)}
+
+ ),
+ }
+ })}
+ />
)
}
diff --git a/frontend/src/lib/components/Subscriptions/subscriptionLogic.ts b/frontend/src/lib/components/Subscriptions/subscriptionLogic.ts
index cf673b42dbe4c..ab5108074621b 100644
--- a/frontend/src/lib/components/Subscriptions/subscriptionLogic.ts
+++ b/frontend/src/lib/components/Subscriptions/subscriptionLogic.ts
@@ -78,7 +78,7 @@ export const subscriptionLogic = kea([
: undefined,
memberOfSlackChannel:
target_type == 'slack'
- ? !values.isMemberOfSlackChannel(target_value)
+ ? target_value && !values.isMemberOfSlackChannel(target_value)
? 'Please add the PostHog Slack App to the selected channel'
: undefined
: undefined,
diff --git a/frontend/src/lib/components/Subscriptions/utils.tsx b/frontend/src/lib/components/Subscriptions/utils.tsx
index 136548cfdfcf1..370c6855d6d1f 100644
--- a/frontend/src/lib/components/Subscriptions/utils.tsx
+++ b/frontend/src/lib/components/Subscriptions/utils.tsx
@@ -1,7 +1,7 @@
import { IconLetter } from '@posthog/icons'
import { LemonSelectOptions } from '@posthog/lemon-ui'
import { IconSlack, IconSlackExternal } from 'lib/lemon-ui/icons'
-import { LemonSelectMultipleOptionItem } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelectOption } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { range } from 'lib/utils'
import { urls } from 'scenes/urls'
@@ -84,7 +84,7 @@ export const timeOptions: LemonSelectOptions = range(0, 24).map((x) => (
export const getSlackChannelOptions = (
value: string,
slackChannels?: SlackChannelType[] | null
-): LemonSelectMultipleOptionItem[] => {
+): LemonInputSelectOption[] => {
return slackChannels
? slackChannels.map((x) => ({
key: `${x.id}|#${x.name}`,
diff --git a/frontend/src/lib/components/Subscriptions/views/EditSubscription.tsx b/frontend/src/lib/components/Subscriptions/views/EditSubscription.tsx
index ed34a261c6103..d6fc5af539bcb 100644
--- a/frontend/src/lib/components/Subscriptions/views/EditSubscription.tsx
+++ b/frontend/src/lib/components/Subscriptions/views/EditSubscription.tsx
@@ -8,13 +8,10 @@ import { IconChevronLeft } from 'lib/lemon-ui/icons'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonField } from 'lib/lemon-ui/LemonField'
+import { LemonInputSelect, LemonInputSelectOption } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { LemonLabel } from 'lib/lemon-ui/LemonLabel/LemonLabel'
import { LemonModal } from 'lib/lemon-ui/LemonModal'
import { LemonSelect } from 'lib/lemon-ui/LemonSelect'
-import {
- LemonSelectMultiple,
- LemonSelectMultipleOptionItem,
-} from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { useEffect, useMemo } from 'react'
import { membersLogic } from 'scenes/organization/membersLogic'
@@ -86,7 +83,7 @@ export function EditSubscription({
}, [subscription?.target_type, slackIntegration])
// If slackChannels aren't loaded, make sure we display only the channel name and not the actual underlying value
- const slackChannelOptions: LemonSelectMultipleOptionItem[] = useMemo(
+ const slackChannelOptions: LemonInputSelectOption[] = useMemo(
() => getSlackChannelOptions(subscription?.target_value, slackChannels),
[slackChannels, subscription?.target_value]
)
@@ -199,11 +196,12 @@ export function EditSubscription({
help="Enter the email addresses of the users you want to share with"
>
{({ value, onChange }) => (
- onChange(val.join(','))}
+ onChange(val.join(','))}
value={value?.split(',').filter(Boolean)}
disabled={emailDisabled}
- mode="multiple-custom"
+ mode="multiple"
+ allowCustomValues
data-attr="subscribed-emails"
options={usersLemonSelectOptions(meFirstMembers.map((x) => x.user))}
loading={membersLoading}
@@ -276,12 +274,13 @@ export function EditSubscription({
}
>
{({ value, onChange }) => (
- onChange(val)}
- value={value}
+ onChange(val[0] ?? null)}
+ value={value ? [value] : []}
disabled={slackDisabled}
mode="single"
data-attr="select-slack-channel"
+ placeholder="Select a channel..."
options={slackChannelOptions}
loading={slackChannelsLoading}
/>
diff --git a/frontend/src/lib/components/Support/SupportForm.tsx b/frontend/src/lib/components/Support/SupportForm.tsx
index 7f92d89f1445a..b23fdd83a3501 100644
--- a/frontend/src/lib/components/Support/SupportForm.tsx
+++ b/frontend/src/lib/components/Support/SupportForm.tsx
@@ -1,4 +1,4 @@
-import { IconBug, IconQuestion } from '@posthog/icons'
+import { IconBug, IconInfo, IconQuestion } from '@posthog/icons'
import {
LemonBanner,
LemonInput,
@@ -6,6 +6,7 @@ import {
LemonSegmentedButtonOption,
lemonToast,
Link,
+ Tooltip,
} from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { Form } from 'kea-forms'
@@ -90,9 +91,12 @@ export function SupportForm(): JSX.Element | null {
>
)}
-
+
+
+
+
{posthog.getFeatureFlag('show-troubleshooting-docs-in-support-form') === 'test-replay-banner' &&
sendSupportRequest.target_area === 'session_replay' && (
@@ -127,18 +131,6 @@ export function SupportForm(): JSX.Element | null {
>
)}
-
-
-
-
- ({
- label: value,
- value: key,
- }))}
- />
-
)}
+
+ <>
+
+
+ Severity level
+
+
+
+
+
+
+
+ Definitions
+
+
+ ({
+ label: value,
+ value: key,
+ }))}
+ />
+ >
+
)
}
diff --git a/frontend/src/lib/components/Support/supportLogic.ts b/frontend/src/lib/components/Support/supportLogic.ts
index 003ad6a4a9e6e..9c4cbd02712b5 100644
--- a/frontend/src/lib/components/Support/supportLogic.ts
+++ b/frontend/src/lib/components/Support/supportLogic.ts
@@ -45,7 +45,7 @@ function getSentryLink(user: UserType | null, cloudRegion: Region | null | undef
}
const SUPPORT_TICKET_KIND_TO_TITLE: Record = {
- support: 'Ask a question',
+ support: 'Contact support',
feedback: 'Give feedback',
bug: 'Report a bug',
}
@@ -237,6 +237,8 @@ export const supportLogic = kea([
openSupportForm: (values: Partial) => values,
submitZendeskTicket: (form: SupportFormFields) => form,
updateUrlParams: true,
+ openEmailForm: true,
+ closeEmailForm: true,
})),
reducers(() => ({
isSupportFormOpen: [
@@ -246,6 +248,13 @@ export const supportLogic = kea([
closeSupportForm: () => false,
},
],
+ isEmailFormOpen: [
+ false,
+ {
+ openEmailForm: () => true,
+ closeEmailForm: () => false,
+ },
+ ],
})),
forms(({ actions, values }) => ({
sendSupportRequest: {
diff --git a/frontend/src/lib/components/UserSelectItem.tsx b/frontend/src/lib/components/UserSelectItem.tsx
index 7990f4c8a7301..903440db365e4 100644
--- a/frontend/src/lib/components/UserSelectItem.tsx
+++ b/frontend/src/lib/components/UserSelectItem.tsx
@@ -1,4 +1,4 @@
-import { LemonSelectMultipleOptionItem } from 'lib/lemon-ui/LemonSelectMultiple'
+import { LemonInputSelectOption } from 'lib/lemon-ui/LemonInputSelect'
import { ProfilePicture } from 'lib/lemon-ui/ProfilePicture'
import { UserBasicType, UserType } from '~/types'
@@ -21,7 +21,7 @@ export function UserSelectItem({ user }: UserSelectItemProps): JSX.Element {
export function usersLemonSelectOptions(
users: (UserBasicType | UserType)[],
key: 'email' | 'uuid' = 'email'
-): LemonSelectMultipleOptionItem[] {
+): LemonInputSelectOption[] {
return users.map((user) => ({
key: user[key],
label: `${user.first_name} ${user.email}`,
diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx
index 77d5f9c37eb0f..522d393f0f9a6 100644
--- a/frontend/src/lib/constants.tsx
+++ b/frontend/src/lib/constants.tsx
@@ -202,6 +202,7 @@ export const FEATURE_FLAGS = {
REPLAY_SIMILAR_RECORDINGS: 'session-replay-similar-recordings', // owner: #team-replay
SAVED_NOT_PINNED: 'saved-not-pinned', // owner: #team-replay
EXPORTS_SIDEPANEL: 'exports-sidepanel', // owner: #team-product-analytics
+ BILLING_UPGRADE_LANGUAGE: 'billing-upgrade-language', // owner: @biancayang
NEW_EXPERIMENTS_UI: 'new-experiments-ui', // owner: @jurajmajerik #team-feature-success
SESSION_REPLAY_V3_INGESTION_PLAYBACK: 'session-replay-v3-ingestion-playback', // owner: @benjackwhite
SESSION_REPLAY_FILTER_ORDERING: 'session-replay-filter-ordering', // owner: #team-replay
diff --git a/frontend/src/lib/lemon-ui/LemonInput/LemonInput.scss b/frontend/src/lib/lemon-ui/LemonInput/LemonInput.scss
index 28a84357dadeb..934cdaeb36254 100644
--- a/frontend/src/lib/lemon-ui/LemonInput/LemonInput.scss
+++ b/frontend/src/lib/lemon-ui/LemonInput/LemonInput.scss
@@ -1,9 +1,11 @@
.LemonInput {
+ --lemon-input-height: calc(2.125rem + 3px); // Medium size button height + button shadow height;
+
display: flex;
gap: 0.25rem;
align-items: center;
- justify-content: center;
- height: calc(2.125rem + 3px); // Medium size button height + button shadow height
+ justify-content: left;
+ height: var(--lemon-input-height);
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
line-height: 1.25rem;
@@ -33,6 +35,7 @@
}
.LemonInput__input {
+ flex: 1;
align-self: stretch; // Improves selectability
width: 100%;
text-overflow: ellipsis;
@@ -56,7 +59,8 @@
}
&.LemonInput--small {
- height: 2rem;
+ --lemon-input-height: 2rem;
+
padding: 0.125rem 0.25rem;
.LemonIcon {
@@ -85,4 +89,10 @@
width: 100%;
max-width: 100%;
}
+
+ .LemonInputSelect & {
+ flex-wrap: wrap;
+ height: auto;
+ min-height: var(--lemon-input-height);
+ }
}
diff --git a/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.stories.tsx b/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.stories.tsx
new file mode 100644
index 0000000000000..796d1794b4c89
--- /dev/null
+++ b/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.stories.tsx
@@ -0,0 +1,102 @@
+import { Meta, StoryFn, StoryObj } from '@storybook/react'
+import { capitalizeFirstLetter } from 'lib/utils'
+import { useState } from 'react'
+
+import { ProfilePicture } from '../ProfilePicture'
+import { LemonInputSelect, LemonInputSelectProps } from './LemonInputSelect'
+
+const names = ['ben', 'marius', 'paul', 'tiina', 'tim', 'james', 'neil', 'tom', 'annika', 'thomas']
+
+type Story = StoryObj
+const meta: Meta = {
+ title: 'Lemon UI/Lemon Input Select',
+ component: LemonInputSelect,
+ args: {
+ options: names.map((x, i) => ({
+ key: `user-${i}`,
+ labelComponent: (
+
+
+
+ {capitalizeFirstLetter(x)} {`<${x}@posthog.com>`}
+
+
+ ),
+ label: `${x} ${x}@posthog.com>`,
+ })),
+ },
+ tags: ['autodocs'],
+}
+export default meta
+
+const Template: StoryFn = (props: LemonInputSelectProps) => {
+ const [value, setValue] = useState(props.value || [])
+ return
+}
+
+export const Default: Story = Template.bind({})
+Default.args = {
+ placeholder: 'Pick one email',
+ mode: 'single',
+}
+
+export const MultipleSelect: Story = Template.bind({})
+MultipleSelect.args = {
+ placeholder: 'Enter emails...',
+ mode: 'multiple',
+}
+
+export const MultipleSelectWithCustom: Story = Template.bind({})
+MultipleSelectWithCustom.args = {
+ placeholder: 'Enter any email...',
+ mode: 'multiple',
+ allowCustomValues: true,
+}
+
+export const Disabled: Story = Template.bind({})
+Disabled.args = {
+ mode: 'single',
+ placeholder: 'Disabled...',
+ disabled: true,
+}
+
+export const Loading: Story = Template.bind({})
+Loading.args = {
+ mode: 'single',
+ placeholder: 'Loading...',
+ options: [],
+ loading: true,
+}
+Loading.parameters = {
+ testOptions: {
+ waitForLoadersToDisappear: false,
+ },
+}
+
+export const NoOptions: Story = Template.bind({})
+NoOptions.args = {
+ mode: 'multiple',
+ allowCustomValues: true,
+ placeholder: 'No options...',
+ options: [],
+}
+
+export const SingleOptionWithCustom: Story = Template.bind({})
+SingleOptionWithCustom.args = {
+ mode: 'single',
+ allowCustomValues: true,
+ placeholder: 'Only one option allowed but can be custom',
+}
+
+export const PrefilledManyValues: Story = Template.bind({})
+PrefilledManyValues.args = {
+ mode: 'multiple',
+ allowCustomValues: true,
+ value: names.map((_, i) => `user-${i}`),
+}
diff --git a/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.tsx b/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.tsx
new file mode 100644
index 0000000000000..f3e39c46f1e11
--- /dev/null
+++ b/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.tsx
@@ -0,0 +1,269 @@
+import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
+import { LemonSnack } from 'lib/lemon-ui/LemonSnack/LemonSnack'
+import { range } from 'lib/utils'
+import { useEffect, useMemo, useRef, useState } from 'react'
+
+import { KeyboardShortcut } from '~/layout/navigation-3000/components/KeyboardShortcut'
+
+import { LemonButton } from '../LemonButton'
+import { LemonDropdown } from '../LemonDropdown'
+import { LemonInput } from '../LemonInput'
+import { PopoverReferenceContext } from '../Popover'
+
+export interface LemonInputSelectOption {
+ key: string
+ label: string
+ labelComponent?: React.ReactNode
+}
+
+export type LemonInputSelectProps = {
+ options?: LemonInputSelectOption[]
+ value?: string[] | null
+ disabled?: boolean
+ loading?: boolean
+ placeholder?: string
+ disableFiltering?: boolean
+ mode: 'multiple' | 'single'
+ allowCustomValues?: boolean
+ onChange?: (newValue: string[]) => void
+ onInputChange?: (newValue: string) => void
+ 'data-attr'?: string
+}
+
+export function LemonInputSelect({
+ placeholder,
+ options = [],
+ value,
+ loading,
+ onChange,
+ onInputChange,
+ mode,
+ disabled,
+ disableFiltering = false,
+ allowCustomValues = false,
+ ...props
+}: LemonInputSelectProps): JSX.Element {
+ const [showPopover, setShowPopover] = useState(false)
+ const [inputValue, _setInputValue] = useState('')
+ const popoverFocusRef = useRef(false)
+ const inputRef = useRef(null)
+ const [selectedIndex, setSelectedIndex] = useState(0)
+ const values = value ?? []
+
+ const visibleOptions = useMemo(() => {
+ const res: LemonInputSelectOption[] = []
+ const customValues = [...values]
+
+ options.forEach((option) => {
+ // Remove from the custom values list if it's in the options
+
+ if (customValues.includes(option.key)) {
+ customValues.splice(customValues.indexOf(option.key), 1)
+ }
+
+ // Check for filtering
+ if (inputValue && !disableFiltering && !option.label.toLowerCase().includes(inputValue.toLowerCase())) {
+ return
+ }
+
+ res.push(option)
+ })
+
+ // Custom values are always shown before the list
+ if (customValues.length) {
+ customValues.forEach((value) => {
+ res.unshift({ key: value, label: value })
+ })
+ }
+
+ // Finally we show the input value if custom values are allowed and it's not in the list
+ if (allowCustomValues && inputValue && !values.includes(inputValue)) {
+ res.unshift({ key: inputValue, label: inputValue })
+ }
+
+ return res
+ }, [options, inputValue, value])
+
+ // Reset the selected index when the visible options change
+ useEffect(() => {
+ setSelectedIndex(0)
+ }, [visibleOptions.length])
+
+ const setInputValue = (newValue: string): void => {
+ _setInputValue(newValue)
+ onInputChange?.(inputValue)
+ }
+
+ const _onActionItem = (item: string): void => {
+ let newValues = [...values]
+ if (values.includes(item)) {
+ // Remove the item
+ if (mode === 'single') {
+ newValues = []
+ } else {
+ newValues.splice(values.indexOf(item), 1)
+ }
+ } else {
+ // Add the item
+ if (mode === 'single') {
+ newValues = [item]
+ } else {
+ newValues.push(item)
+ }
+
+ setInputValue('')
+ }
+
+ onChange?.(newValues)
+ }
+
+ const _onBlur = (): void => {
+ // We need to add a delay as a click could be in the popover or the input wrapper which refocuses
+ setTimeout(() => {
+ if (popoverFocusRef.current) {
+ popoverFocusRef.current = false
+ inputRef.current?.focus()
+ _onFocus()
+ return
+ }
+ if (allowCustomValues && inputValue.trim() && !values.includes(inputValue)) {
+ _onActionItem(inputValue.trim())
+ } else {
+ setInputValue('')
+ }
+ setShowPopover(false)
+ }, 100)
+ }
+
+ const _onFocus = (): void => {
+ setShowPopover(true)
+ popoverFocusRef.current = true
+ }
+
+ const _onKeyDown = (e: React.KeyboardEvent): void => {
+ if (e.key === 'Enter') {
+ e.preventDefault()
+
+ const itemToAdd = visibleOptions[selectedIndex]?.key
+ if (itemToAdd) {
+ _onActionItem(visibleOptions[selectedIndex]?.key)
+ }
+ } else if (e.key === 'Backspace') {
+ if (!inputValue) {
+ e.preventDefault()
+ const newValues = [...values]
+ newValues.pop()
+ onChange?.(newValues)
+ }
+ } else if (e.key === 'ArrowDown') {
+ e.preventDefault()
+ setSelectedIndex(Math.min(selectedIndex + 1, options.length - 1))
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault()
+ setSelectedIndex(Math.max(selectedIndex - 1, 0))
+ }
+ }
+
+ // TRICKY: We don't want the popover to affect the snack buttons
+ const prefix = (
+
+ <>
+ {values.map((value) => {
+ const option = options.find((option) => option.key === value) ?? {
+ label: value,
+ labelComponent: null,
+ }
+ return (
+ <>
+ _onActionItem(value)}>
+ {option?.labelComponent ?? option?.label}
+
+ >
+ )
+ })}
+ >
+
+ )
+
+ return (
+ {
+ popoverFocusRef.current = false
+ setShowPopover(false)
+ }}
+ onClickInside={(e) => {
+ popoverFocusRef.current = true
+ e.stopPropagation()
+ }}
+ overlay={
+
+ {visibleOptions.length ? (
+ visibleOptions?.map((option, index) => {
+ const isHighlighted = index === selectedIndex
+ return (
+
_onActionItem(option.key)}
+ onMouseEnter={() => setSelectedIndex(index)}
+ >
+
+ {option.labelComponent ?? option.label}
+ {isHighlighted ? (
+
+ {' '}
+ {!values.includes(option.key)
+ ? mode === 'single'
+ ? 'select'
+ : 'add'
+ : mode === 'single'
+ ? 'unselect'
+ : 'remove'}
+
+ ) : undefined}
+
+
+ )
+ })
+ ) : loading ? (
+ <>
+ {range(5).map((x) => (
+
+
+
+
+ ))}
+ >
+ ) : (
+
+ {allowCustomValues
+ ? 'Start typing and press Enter to add options'
+ : `No options matching "${inputValue}"`}
+
+ )}
+
+ }
+ >
+
+
+
+
+ )
+}
diff --git a/frontend/src/lib/lemon-ui/LemonInputSelect/index.ts b/frontend/src/lib/lemon-ui/LemonInputSelect/index.ts
new file mode 100644
index 0000000000000..4e01ced8508e3
--- /dev/null
+++ b/frontend/src/lib/lemon-ui/LemonInputSelect/index.ts
@@ -0,0 +1 @@
+export * from './LemonInputSelect'
diff --git a/frontend/src/lib/lemon-ui/LemonModal/LemonModal.tsx b/frontend/src/lib/lemon-ui/LemonModal/LemonModal.tsx
index a4dd176d9511b..3a50ea89355f8 100644
--- a/frontend/src/lib/lemon-ui/LemonModal/LemonModal.tsx
+++ b/frontend/src/lib/lemon-ui/LemonModal/LemonModal.tsx
@@ -45,6 +45,7 @@ export interface LemonModalProps {
forceAbovePopovers?: boolean
contentRef?: React.RefCallback
overlayRef?: React.RefCallback
+ 'data-attr'?: string
}
export const LemonModalHeader = ({ children, className }: LemonModalInnerProps): JSX.Element => {
@@ -82,6 +83,7 @@ export function LemonModal({
contentRef,
overlayRef,
hideCloseButton = false,
+ 'data-attr': dataAttr,
}: LemonModalProps): JSX.Element {
const nodeRef = useRef(null)
const [ignoredOverlayClickCount, setIgnoredOverlayClickCount] = useState(0)
@@ -89,7 +91,7 @@ export function LemonModal({
useEffect(() => setIgnoredOverlayClickCount(0), [hasUnsavedInput]) // Reset when there no longer is unsaved input
const modalContent = (
-
+
{closable && !hideCloseButton && (
// The key causes the div to be re-rendered, which restarts the animation,
// providing immediate visual feedback on click
diff --git a/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.scss b/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.scss
deleted file mode 100644
index 67200ac17bdf3..0000000000000
--- a/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.scss
+++ /dev/null
@@ -1,118 +0,0 @@
-.LemonSelectMultiple {
- .ant-select {
- width: 100%;
-
- .ant-select-selector,
- &.ant-select-single .ant-select-selector {
- min-height: 2.125rem;
- padding: 0.25rem;
- font-size: 0.875rem;
- line-height: 1.25rem;
- text-align: left;
- background: var(--bg-light);
- border: 1px solid var(--border);
- border-radius: var(--radius);
-
- .ant-select-selection-overflow {
- gap: 0.25rem;
- }
- }
-
- &:not(.ant-select-disabled):hover,
- &.ant-select-focused:not(.ant-select-disabled),
- &:not(.ant-select-disabled):active {
- .ant-select-selector {
- background: var(--bg-light);
- border-color: var(--border-bold);
- box-shadow: none;
- }
- }
-
- &:not(.ant-select-disabled):active {
- .ant-select-selector {
- color: var(--primary-active);
- }
- }
-
- .ant-select-selection-placeholder {
- color: var(--muted);
- }
-
- &.ant-select-single {
- .ant-select-selector {
- box-sizing: border-box;
- height: 40px;
-
- .ant-select-selection-search-input {
- height: 38px;
- }
-
- .ant-select-selection-placeholder {
- padding-left: 0.4rem;
- }
-
- .ant-select-selection-item {
- padding-left: 0.4rem;
- }
- }
- }
-
- .ant-select-arrow {
- display: none;
- }
- }
-}
-
-.LemonSelectMultipleDropdown {
- padding: 0.5rem;
- margin: -4px 0; // Counteract antd wrapper
- background: var(--bg-light);
- border: 1px solid var(--primary-3000);
- border-radius: var(--radius);
-
- .ant-select-item {
- padding: 0;
- padding-bottom: 0.2rem;
- background: none;
-
- .ant-select-item-option-content {
- height: 40px;
- padding: 0.25rem 0.5rem;
- cursor: pointer;
- border-radius: var(--radius);
- }
-
- &.ant-select-item-option-active {
- .ant-select-item-option-content {
- background: var(--primary-bg-hover);
- }
- }
-
- &.ant-select-item-option-selected {
- .ant-select-item-option-content {
- background: var(--primary-bg-active);
- }
- }
-
- .ant-select-item-option-state {
- display: none;
- }
- }
-
- .ant-select-item-empty {
- padding: 0;
- }
-
- .ant-select-item-option-content {
- display: flex;
- align-items: center;
- }
-
- .LemonSelectMultipleDropdown__skeleton {
- display: flex;
- gap: 0.5rem;
- align-items: center;
- height: 40px;
- padding: 0 0.25rem;
- }
-}
diff --git a/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.stories.tsx b/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.stories.tsx
deleted file mode 100644
index baa2f805f48e4..0000000000000
--- a/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.stories.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Meta, StoryFn, StoryObj } from '@storybook/react'
-import { capitalizeFirstLetter } from 'lib/utils'
-import { useState } from 'react'
-
-import { ProfilePicture } from '../ProfilePicture'
-import { LemonSelectMultiple, LemonSelectMultipleProps } from './LemonSelectMultiple'
-
-type Story = StoryObj
-const meta: Meta = {
- title: 'Lemon UI/Lemon SelectMultiple',
- component: LemonSelectMultiple,
- args: {
- options: ['ben', 'marius', 'paul', 'tiina', 'li'].reduce(
- (acc, x, i) => ({
- ...acc,
- [`user-${i}`]: {
- labelComponent: (
-
-
-
- {capitalizeFirstLetter(x)} {`<${x}@posthog.com>`}
-
-
- ),
- label: `${x} ${x}@posthog.com>`,
- },
- }),
- {}
- ),
- },
- tags: ['autodocs'],
-}
-export default meta
-
-const Template: StoryFn = (props: LemonSelectMultipleProps) => {
- const [value, setValue] = useState(props.value || [])
- return
-}
-
-export const Default: Story = Template.bind({})
-Default.args = {
- placeholder: 'Pick one email',
-}
-
-export const MultipleSelect: Story = Template.bind({})
-MultipleSelect.args = {
- placeholder: 'Enter emails...',
- mode: 'multiple',
-}
-
-export const MultipleSelectWithCustom: Story = Template.bind({})
-MultipleSelectWithCustom.args = {
- placeholder: 'Enter any email...',
- mode: 'multiple-custom',
-}
-
-export const Disabled: Story = Template.bind({})
-Disabled.args = {
- placeholder: 'Disabled...',
- disabled: true,
-}
-
-export const Loading: Story = Template.bind({})
-Loading.args = {
- placeholder: 'Loading...',
- options: [],
- loading: true,
-}
-Loading.parameters = {
- testOptions: {
- waitForLoadersToDisappear: false,
- },
-}
-
-export const NoOptions: Story = Template.bind({})
-NoOptions.args = {
- mode: 'multiple-custom',
- placeholder: 'No options...',
- options: [],
-}
diff --git a/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.tsx b/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.tsx
deleted file mode 100644
index 9c085799bb855..0000000000000
--- a/frontend/src/lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import './LemonSelectMultiple.scss'
-
-import { Select } from 'antd'
-import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
-import { LemonSnack } from 'lib/lemon-ui/LemonSnack/LemonSnack'
-import { range } from 'lib/utils'
-import { ReactNode } from 'react'
-
-export interface LemonSelectMultipleOption {
- label: string
- disabled?: boolean
- 'data-attr'?: string
- labelComponent?: React.ReactNode
-}
-
-export interface LemonSelectMultipleOptionItem extends LemonSelectMultipleOption {
- key: string
-}
-
-export type LemonSelectMultipleOptions = Record
-
-export type LemonSelectMultipleProps = {
- selectClassName?: string
- options?: LemonSelectMultipleOptions | LemonSelectMultipleOptionItem[]
- value?: string | string[] | null
- disabled?: boolean
- loading?: boolean
- placeholder?: string
- labelInValue?: boolean
- onSearch?: (value: string) => void
- onFocus?: () => void
- onBlur?: () => void
- filterOption?: boolean
- mode?: 'single' | 'multiple' | 'multiple-custom'
- onChange?: ((newValue: string) => void) | ((newValue: string[]) => void)
- 'data-attr'?: string
-}
-
-export type LabelInValue = { value: string; label: ReactNode }
-
-export function LemonSelectMultiple({
- value,
- options,
- disabled,
- loading,
- placeholder,
- labelInValue,
- onChange,
- onSearch,
- onFocus,
- onBlur,
- filterOption = true,
- mode = 'single',
- selectClassName,
- ...props
-}: LemonSelectMultipleProps): JSX.Element {
- const optionsAsList: LemonSelectMultipleOptionItem[] = Array.isArray(options)
- ? options
- : Object.entries(options || {}).map(([key, option]) => ({
- key: key,
- ...option,
- }))
-
- const antOptions = optionsAsList.map((option) => ({
- key: option.key,
- value: option.key,
- label: option.labelComponent || option.label,
- labelString: option.label,
- }))
-
- return (
-
-
{
- if (onChange) {
- // TRICKY: V is typed poorly and will be a string if the "mode" is undefined
- if (!v || typeof v === 'string') {
- const typedValues = v as string | null
- const typedOnChange = onChange as (newValue: string | null) => void
- typedOnChange(typedValues)
- } else {
- const typedValues = v.map((token) => token.toString().trim())
- const typedOnChange = onChange as (newValue: string[]) => void
- typedOnChange(typedValues)
- }
- }
- }}
- tokenSeparators={[',']}
- value={value ? value : []}
- dropdownRender={(menu) => {menu}
}
- optionFilterProp="labelString"
- options={antOptions}
- placeholder={placeholder}
- notFoundContent={
- loading ? (
-
- {range(5).map((x) => (
-
-
-
-
- ))}
-
- ) : null
- }
- filterOption={filterOption}
- tagRender={({ label, onClose }) => {label} }
- />
-
- )
-}
diff --git a/frontend/src/lib/lemon-ui/LemonSelectMultiple/index.ts b/frontend/src/lib/lemon-ui/LemonSelectMultiple/index.ts
deleted file mode 100644
index bdc775044550f..0000000000000
--- a/frontend/src/lib/lemon-ui/LemonSelectMultiple/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './LemonSelectMultiple'
diff --git a/frontend/src/lib/utils/eventUsageLogic.ts b/frontend/src/lib/utils/eventUsageLogic.ts
index d4c6a9be07d2c..069d1758d0e56 100644
--- a/frontend/src/lib/utils/eventUsageLogic.ts
+++ b/frontend/src/lib/utils/eventUsageLogic.ts
@@ -502,8 +502,12 @@ export const eventUsageLogic = kea([
reportCommandBarSearchResultOpened: (type: ResultType) => ({ type }),
reportCommandBarActionSearch: (query: string) => ({ query }),
reportCommandBarActionResultExecuted: (resultDisplay) => ({ resultDisplay }),
+ reportBillingCTAShown: true,
}),
listeners(({ values }) => ({
+ reportBillingCTAShown: () => {
+ posthog.capture('billing CTA shown')
+ },
reportAxisUnitsChanged: (properties) => {
posthog.capture('axis units changed', properties)
},
diff --git a/frontend/src/scenes/ResourcePermissionModal.tsx b/frontend/src/scenes/ResourcePermissionModal.tsx
index 8878441534132..0735b3d7b5f7d 100644
--- a/frontend/src/scenes/ResourcePermissionModal.tsx
+++ b/frontend/src/scenes/ResourcePermissionModal.tsx
@@ -2,10 +2,7 @@ import { IconGear, IconTrash } from '@posthog/icons'
import { LemonButton, LemonModal, LemonTable } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { TitleWithIcon } from 'lib/components/TitleWithIcon'
-import {
- LemonSelectMultiple,
- LemonSelectMultipleOptionItem,
-} from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect, LemonInputSelectOption } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { LemonTableColumns } from 'lib/lemon-ui/LemonTable'
import { AccessLevel, Resource, RoleType } from '~/types'
@@ -36,7 +33,7 @@ interface ResourcePermissionModalProps extends ResourcePermissionProps {
onClose: () => void
}
-export function roleLemonSelectOptions(roles: RoleType[]): LemonSelectMultipleOptionItem[] {
+export function roleLemonSelectOptions(roles: RoleType[]): LemonInputSelectOption[] {
return roles.map((role) => ({
key: role.id,
label: `${role.name}`,
@@ -201,12 +198,11 @@ export function ResourcePermission({
Custom edit roles
-
- <>
+
You should have access to this feature already. If you are still seeing this modal, please let
us know 🙂
- >
+
diff --git a/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx b/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx
index 58a204f04f249..106da039cd22d 100644
--- a/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx
+++ b/frontend/src/scenes/batch_exports/BatchExportEditForm.tsx
@@ -7,7 +7,7 @@ import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { LemonCalendarSelectInput } from 'lib/lemon-ui/LemonCalendar/LemonCalendarSelect'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { LemonFileInput } from 'lib/lemon-ui/LemonFileInput/LemonFileInput'
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
@@ -268,15 +268,17 @@ export function BatchExportsEditFields({
-
-
@@ -317,15 +319,17 @@ export function BatchExportsEditFields({
-
-
@@ -376,15 +380,17 @@ export function BatchExportsEditFields({
-
-
@@ -431,15 +437,17 @@ export function BatchExportsEditFields({
-
-
@@ -476,15 +484,17 @@ export function BatchExportsEditFields({
) : null}
-
-
@@ -499,15 +509,17 @@ export function BatchExportsEditFields({
-
-
diff --git a/frontend/src/scenes/billing/Billing.tsx b/frontend/src/scenes/billing/Billing.tsx
index e3e5d3575500f..e418e66c52309 100644
--- a/frontend/src/scenes/billing/Billing.tsx
+++ b/frontend/src/scenes/billing/Billing.tsx
@@ -6,15 +6,18 @@ import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { Field, Form } from 'kea-forms'
import { router } from 'kea-router'
+import { BillingUpgradeCTA } from 'lib/components/BillingUpgradeCTA'
import { SurprisedHog } from 'lib/components/hedgehogs'
import { PageHeader } from 'lib/components/PageHeader'
import { supportLogic } from 'lib/components/Support/supportLogic'
+import { FEATURE_FLAGS } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { LemonLabel } from 'lib/lemon-ui/LemonLabel/LemonLabel'
import { SpinnerOverlay } from 'lib/lemon-ui/Spinner/Spinner'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
+import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { useEffect } from 'react'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { SceneExport } from 'scenes/sceneTypes'
@@ -48,6 +51,7 @@ export function Billing(): JSX.Element {
const { reportBillingV2Shown } = useActions(billingLogic)
const { preflight, isCloudOrDev } = useValues(preflightLogic)
const { openSupportForm } = useActions(supportLogic)
+ const { featureFlags } = useValues(featureFlagLogic)
if (preflight && !isCloudOrDev) {
router.actions.push(urls.default())
@@ -310,14 +314,22 @@ export function Billing(): JSX.Element {
Products
{isOnboarding && upgradeAllProductsLink && (
- }
to={upgradeAllProductsLink}
disableClientSideRouting
>
- Upgrade all
-
+ {featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'Subscribe to all'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ !billing?.has_active_subscription
+ ? 'Add credit card to all products'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ billing?.has_active_subscription
+ ? 'Add all products to plan'
+ : 'Upgrade to all'}{' '}
+
)}
diff --git a/frontend/src/scenes/billing/BillingHero.tsx b/frontend/src/scenes/billing/BillingHero.tsx
index ca8e8170a5832..726bc4775a251 100644
--- a/frontend/src/scenes/billing/BillingHero.tsx
+++ b/frontend/src/scenes/billing/BillingHero.tsx
@@ -1,10 +1,17 @@
import './BillingHero.scss'
+import { useValues } from 'kea'
import { BlushingHog } from 'lib/components/hedgehogs'
+import { FEATURE_FLAGS } from 'lib/constants'
+import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import useResizeObserver from 'use-resize-observer'
+import { billingLogic } from './billingLogic'
+
export const BillingHero = (): JSX.Element => {
const { width, ref: billingHeroRef } = useResizeObserver()
+ const { featureFlags } = useValues(featureFlagLogic)
+ const { billing } = useValues(billingLogic)
return (
@@ -13,8 +20,17 @@ export const BillingHero = (): JSX.Element => {
Get the whole hog.
Only pay for what you use.
- Add your credit card details to get access to premium product and platform features. Set billing
- limits as low as $0 to control spend.
+ {featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'Subscribe'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ !billing?.has_active_subscription
+ ? 'Add your credit card'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ billing?.has_active_subscription
+ ? 'Add the paid plan'
+ : 'Upgrade'}{' '}
+ to get access to premium product and platform features. Set billing limits as low as $0 to control
+ spend.
{width && width > 500 && (
diff --git a/frontend/src/scenes/billing/BillingProduct.tsx b/frontend/src/scenes/billing/BillingProduct.tsx
index 78a7754854652..39e5fc0c3d63e 100644
--- a/frontend/src/scenes/billing/BillingProduct.tsx
+++ b/frontend/src/scenes/billing/BillingProduct.tsx
@@ -2,7 +2,8 @@ import { IconCheckCircle, IconChevronDown, IconDocument, IconInfo, IconPlus } fr
import { LemonButton, LemonSelectOptions, LemonTable, LemonTag, Link } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
-import { UNSUBSCRIBE_SURVEY_ID } from 'lib/constants'
+import { BillingUpgradeCTA } from 'lib/components/BillingUpgradeCTA'
+import { FEATURE_FLAGS, UNSUBSCRIBE_SURVEY_ID } from 'lib/constants'
import { useResizeBreakpoints } from 'lib/hooks/useResizeObserver'
import { IconChevronRight } from 'lib/lemon-ui/icons'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
@@ -211,6 +212,7 @@ export const BillingProduct = ({ product }: { product: BillingProductV2Type }):
} = useActions(billingProductLogic({ product, productRef }))
const { reportBillingUpgradeClicked } = useActions(eventUsageLogic)
+ const { featureFlags } = useValues(featureFlagLogic)
const upgradePlan = currentAndUpgradePlans?.upgradePlan
const currentPlan = currentAndUpgradePlans?.currentPlan
const downgradePlan = currentAndUpgradePlans?.downgradePlan
@@ -609,8 +611,18 @@ export const BillingProduct = ({ product }: { product: BillingProductV2Type }):
{additionalFeaturesOnUpgradedPlan?.length > 0 ? (
<>
- {!upgradePlan ? 'You now' : `Upgrade to the ${upgradePlan.name} plan to`} get
- sweet features such as:
+ {product.subscribed
+ ? 'You now'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'Subscribe to'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ !billing?.has_active_subscription
+ ? 'Add a credit card to'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ billing?.has_active_subscription
+ ? 'Add paid plan'
+ : 'Upgrade to'}{' '}
+ get sweet features such as:
{additionalFeaturesOnUpgradedPlan?.map((feature, i) => {
@@ -682,7 +694,8 @@ export const BillingProduct = ({ product }: { product: BillingProductV2Type }):
) : (
upgradePlan.included_if !== 'has_subscription' &&
!upgradePlan.unit_amount_usd && (
-
- Upgrade
-
+ {featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'Subscribe'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] ===
+ 'credit_card' && !billing?.has_active_subscription
+ ? 'Add credit card'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] ===
+ 'credit_card' && billing?.has_active_subscription
+ ? 'Add paid plan'
+ : 'Upgrade'}
+
)
)}
diff --git a/frontend/src/scenes/billing/PlanComparison.tsx b/frontend/src/scenes/billing/PlanComparison.tsx
index 09cd31af31e21..8144d5c327e46 100644
--- a/frontend/src/scenes/billing/PlanComparison.tsx
+++ b/frontend/src/scenes/billing/PlanComparison.tsx
@@ -1,12 +1,14 @@
import './PlanComparison.scss'
import { IconCheckCircle, IconWarning, IconX } from '@posthog/icons'
-import { LemonButton, LemonModal, LemonTag, Link } from '@posthog/lemon-ui'
+import { LemonModal, LemonTag, Link } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
-import { UNSUBSCRIBE_SURVEY_ID } from 'lib/constants'
+import { BillingUpgradeCTA } from 'lib/components/BillingUpgradeCTA'
+import { FEATURE_FLAGS, UNSUBSCRIBE_SURVEY_ID } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
+import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import React from 'react'
import { getProductIcon } from 'scenes/products/Products'
@@ -119,6 +121,7 @@ export const PlanComparison = ({
const { billing, redirectPath } = useValues(billingLogic)
const { width, ref: planComparisonRef } = useResizeObserver()
const { reportBillingUpgradeClicked } = useActions(eventUsageLogic)
+ const { featureFlags } = useValues(featureFlagLogic)
const currentPlanIndex = plans.findIndex((plan) => plan.current_plan)
const { surveyID, comparisonModalHighlightedFeatureKey } = useValues(billingProductLogic({ product }))
const { reportSurveyShown, setSurveyResponse } = useActions(billingProductLogic({ product }))
@@ -131,7 +134,7 @@ export const PlanComparison = ({
const upgradeButtons = plans?.map((plan, i) => {
return (
- = currentPlanIndex &&
!billing?.has_active_subscription
? 'View products'
- : 'Subscribe'}
-
+ : plan.free_allocation && !plan.tiers
+ ? 'Select' // Free plan
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'Subscribe'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ !billing?.has_active_subscription
+ ? 'Add credit card'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ billing?.has_active_subscription
+ ? 'Add paid plan'
+ : 'Upgrade'}
+
{!plan.current_plan && !plan.free_allocation && includeAddons && product.addons?.length > 0 && (
- or subscribe without addons
+ or{' '}
+ {featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'subscribe'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ !billing?.has_active_subscription
+ ? 'add credit card'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ !billing?.has_active_subscription
+ ? 'add paid plan'
+ : 'upgrade'}{' '}
+ without addons
)}
diff --git a/frontend/src/scenes/cohorts/CohortFilters/CohortField.scss b/frontend/src/scenes/cohorts/CohortFilters/CohortField.scss
index 45cb5e9a6620a..c8d0027fb0850 100644
--- a/frontend/src/scenes/cohorts/CohortFilters/CohortField.scss
+++ b/frontend/src/scenes/cohorts/CohortFilters/CohortField.scss
@@ -16,43 +16,3 @@
.CohortField__CohortNumberField {
max-width: 4rem;
}
-
-.CohortField__CohortPersonPropertiesValuesField {
- min-width: 150px;
- min-height: 40px;
- border: 1px solid var(--border);
- border-radius: var(--radius);
-
- .ant-select-selection-search,
- .ant-select-selection-placeholder {
- display: flex;
- align-items: center;
- padding: 0 4px !important;
- }
-
- &.ant-select-single {
- .ant-select-selector {
- height: unset;
- min-height: 38px !important;
- background-color: inherit;
- border: none !important;
- }
- }
-
- &.ant-select-multiple {
- .ant-select-selector {
- height: 100% !important;
- padding: 5px 40px 5px 11px;
- background-color: inherit;
- border: none !important;
-
- .ant-select-selection-search {
- padding-left: 0 !important;
- }
-
- .ant-select-selection-placeholder {
- padding-left: 6px !important;
- }
- }
- }
-}
diff --git a/frontend/src/scenes/cohorts/CohortFilters/CohortField.tsx b/frontend/src/scenes/cohorts/CohortFilters/CohortField.tsx
index 2649724db300a..eb30b33f72298 100644
--- a/frontend/src/scenes/cohorts/CohortFilters/CohortField.tsx
+++ b/frontend/src/scenes/cohorts/CohortFilters/CohortField.tsx
@@ -155,7 +155,6 @@ export function CohortPersonPropertiesValuesField({
return (
-
setExplicitCollaboratorsToBeAdded(newValues)
}
- filterOption={true}
mode="multiple"
data-attr="subscribed-emails"
options={usersLemonSelectOptions(addableMembers, 'uuid')}
diff --git a/frontend/src/scenes/insights/EditorFilters/PathsWildcardGroups.tsx b/frontend/src/scenes/insights/EditorFilters/PathsWildcardGroups.tsx
index 17d2e6dd67e0a..0325fc2358249 100644
--- a/frontend/src/scenes/insights/EditorFilters/PathsWildcardGroups.tsx
+++ b/frontend/src/scenes/insights/EditorFilters/PathsWildcardGroups.tsx
@@ -1,5 +1,5 @@
import { useActions, useValues } from 'kea'
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { pathsDataLogic } from 'scenes/paths/pathsDataLogic'
import { EditorFilterProps } from '~/types'
@@ -9,11 +9,12 @@ export function PathsWildcardGroups({ insightProps }: EditorFilterProps): JSX.El
const { updateInsightFilter } = useActions(pathsDataLogic(insightProps))
return (
- updateInsightFilter({ pathGroupings })}
value={pathsFilter?.pathGroupings || []}
- filterOption={false}
- mode="multiple-custom"
+ disableFiltering
+ mode="multiple"
+ allowCustomValues
/>
)
}
diff --git a/frontend/src/scenes/instance/SystemStatus/StaffUsersTab.tsx b/frontend/src/scenes/instance/SystemStatus/StaffUsersTab.tsx
index 0ad0047b94879..a246cd3cd6330 100644
--- a/frontend/src/scenes/instance/SystemStatus/StaffUsersTab.tsx
+++ b/frontend/src/scenes/instance/SystemStatus/StaffUsersTab.tsx
@@ -3,7 +3,7 @@ import { LemonDivider, LemonModal, Link } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { usersLemonSelectOptions } from 'lib/components/UserSelectItem'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { LemonTable, LemonTableColumns } from 'lib/lemon-ui/LemonTable'
import { LemonTag } from 'lib/lemon-ui/LemonTag/LemonTag'
import { ProfilePicture } from 'lib/lemon-ui/ProfilePicture'
@@ -87,12 +87,11 @@ export function StaffUsersTab(): JSX.Element {
-
setStaffUsersToBeAdded(newValues)}
- filterOption={true}
mode="multiple"
data-attr="subscribed-emails"
options={usersLemonSelectOptions(nonStaffUsers, 'uuid')}
diff --git a/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx b/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx
index 9eb5ce2db1332..b5350dbb0c2f4 100644
--- a/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx
+++ b/frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx
@@ -1,4 +1,4 @@
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect'
import posthog from 'posthog-js'
import { NotebooksListFilters } from 'scenes/notebooks/NotebooksTable/notebooksTableLogic'
@@ -38,16 +38,12 @@ export function ContainsTypeFilters({
return (
Containing:
-
entry[1] !== '')
- .reduce((acc, [type, label]) => {
- acc[type] = { label }
- return acc
- }, {})}
+ .map(([type, label]) => ({ key: type, label }))}
value={filters.contains}
onChange={(newValue: string[]) => {
posthog.capture('notebook containing filter applied')
diff --git a/frontend/src/scenes/onboarding/OnboardingBillingStep.tsx b/frontend/src/scenes/onboarding/OnboardingBillingStep.tsx
index be78629f06f26..c00a4aab1ebde 100644
--- a/frontend/src/scenes/onboarding/OnboardingBillingStep.tsx
+++ b/frontend/src/scenes/onboarding/OnboardingBillingStep.tsx
@@ -1,8 +1,11 @@
import { IconCheckCircle } from '@posthog/icons'
import { LemonBanner, LemonButton } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
+import { BillingUpgradeCTA } from 'lib/components/BillingUpgradeCTA'
import { StarHog } from 'lib/components/hedgehogs'
+import { FEATURE_FLAGS } from 'lib/constants'
import { Spinner } from 'lib/lemon-ui/Spinner'
+import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { useState } from 'react'
import { getUpgradeProductLink } from 'scenes/billing/billing-utils'
@@ -29,6 +32,7 @@ export const OnboardingBillingStep = ({
const { reportBillingUpgradeClicked } = useActions(eventUsageLogic)
const plan = currentAndUpgradePlans?.upgradePlan
const currentPlan = currentAndUpgradePlans?.currentPlan
+ const { featureFlags } = useValues(featureFlagLogic)
const [showPlanComp, setShowPlanComp] = useState(false)
@@ -39,7 +43,7 @@ export const OnboardingBillingStep = ({
stepKey={stepKey}
continueOverride={
product?.subscribed ? undefined : (
-
- Subscribe to Paid Plan
-
+ {featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'Subscribe to paid plan'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ !billing?.has_active_subscription
+ ? 'Add credit card to get paid features'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'credit_card' &&
+ billing?.has_active_subscription
+ ? 'Add paid plan'
+ : 'Upgrade to paid plan'}
+
)
}
>
@@ -63,7 +75,17 @@ export const OnboardingBillingStep = ({
-
Subscribe successful
+
+ {featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] === 'subscribe'
+ ? 'Subscribe successful'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] ===
+ 'credit_card' && !billing?.has_active_subscription
+ ? 'Successfully added credit card'
+ : featureFlags[FEATURE_FLAGS.BILLING_UPGRADE_LANGUAGE] ===
+ 'credit_card' && !billing?.has_active_subscription
+ ? 'Successfully added paid plan'
+ : 'Upgrade successful'}
+
You're all ready to use {product.name}.
diff --git a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts
index 22fe284c3a504..bf59f450be440 100644
--- a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts
+++ b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts
@@ -175,7 +175,7 @@ export const playerSettingsLogic = kea([
setSpeed: (speed: number) => ({ speed }),
setShowOnlyMatching: (showOnlyMatching: boolean) => ({ showOnlyMatching }),
setHideViewedRecordings: (hideViewedRecordings: boolean) => ({ hideViewedRecordings }),
- toggleAutoplayDirection: true,
+ setAutoplayDirection: (autoplayDirection: AutoplayDirection) => ({ autoplayDirection }),
setTab: (tab: SessionRecordingPlayerTab) => ({ tab }),
setTimestampMode: (mode: 'absolute' | 'relative') => ({ mode }),
setMiniFilter: (key: string, enabled: boolean) => ({ key, enabled }),
@@ -246,9 +246,7 @@ export const playerSettingsLogic = kea([
'older' as AutoplayDirection,
{ persist: true },
{
- toggleAutoplayDirection: (state) => {
- return !state ? 'older' : state === 'older' ? 'newer' : null
- },
+ setAutoplayDirection: (_, { autoplayDirection }) => autoplayDirection,
},
],
hideViewedRecordings: [
diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx
index 801cb3bf61472..7f1e25df34b84 100644
--- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx
+++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistSettings.tsx
@@ -1,8 +1,5 @@
-import { IconPlay } from '@posthog/icons'
-import { LemonSwitch } from '@posthog/lemon-ui'
-import clsx from 'clsx'
+import { LemonSelect, LemonSwitch } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
-import { IconPause } from 'lib/lemon-ui/icons'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { DurationTypeSelect } from 'scenes/session-recordings/filters/DurationTypeSelect'
@@ -11,41 +8,37 @@ import { sessionRecordingsPlaylistLogic } from './sessionRecordingsPlaylistLogic
export function SessionRecordingsPlaylistSettings(): JSX.Element {
const { autoplayDirection, durationTypeToShow, hideViewedRecordings } = useValues(playerSettingsLogic)
- const { toggleAutoplayDirection, setDurationTypeToShow, setHideViewedRecordings } = useActions(playerSettingsLogic)
+ const { setAutoplayDirection, setDurationTypeToShow, setHideViewedRecordings } = useActions(playerSettingsLogic)
const { orderBy } = useValues(sessionRecordingsPlaylistLogic)
return (
-
- Autoplay
-
- Autoplay next recording
- ({!autoplayDirection ? 'disabled' : autoplayDirection})
-
- }
- placement="bottom"
- >
-
+ Autoplay next recording
+ ({!autoplayDirection ? 'off' : autoplayDirection})
+
+ }
+ placement="right"
+ >
+
+ Autoplay
+
+
- {autoplayDirection ? : }
-
- }
+ onChange={setAutoplayDirection}
+ dropdownMatchSelectWidth={false}
+ options={[
+ { value: null, label: 'off' },
+ { value: 'newer', label: 'newer recordings' },
+ { value: 'older', label: 'older recordings' },
+ ]}
+ size="small"
/>
-
-
+
+
Hide viewed
Members
-
setRoleMembersToAdd(newValues)}
- filterOption={true}
mode="multiple"
data-attr="subscribed-emails"
options={usersLemonSelectOptions(addableMembers, 'uuid')}
diff --git a/frontend/src/scenes/settings/project/AddMembersModal.tsx b/frontend/src/scenes/settings/project/AddMembersModal.tsx
index 40f81262cf796..1bd544eb04fdd 100644
--- a/frontend/src/scenes/settings/project/AddMembersModal.tsx
+++ b/frontend/src/scenes/settings/project/AddMembersModal.tsx
@@ -6,7 +6,7 @@ import { RestrictedComponentProps } from 'lib/components/RestrictedArea'
import { usersLemonSelectOptions } from 'lib/components/UserSelectItem'
import { TeamMembershipLevel } from 'lib/constants'
import { LemonField } from 'lib/lemon-ui/LemonField'
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { membershipLevelToName, teamMembershipLevelIntegers } from 'lib/utils/permissioning'
import { useState } from 'react'
import { sceneLogic } from 'scenes/sceneLogic'
@@ -56,7 +56,7 @@ export function AddMembersModalWithButton({ isRestricted }: RestrictedComponentP
-
-
handleChange(undefined, undefined, properties)}
value={funnelCorrelationConfig.excluded_event_property_names || []}
/>
diff --git a/frontend/src/scenes/settings/project/DataAttributes.tsx b/frontend/src/scenes/settings/project/DataAttributes.tsx
index d1d090ebfa633..b1a62f5507e2f 100644
--- a/frontend/src/scenes/settings/project/DataAttributes.tsx
+++ b/frontend/src/scenes/settings/project/DataAttributes.tsx
@@ -1,6 +1,6 @@
import { LemonButton, LemonSkeleton, Link } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { useEffect, useState } from 'react'
import { teamLogic } from 'scenes/teamLogic'
@@ -18,7 +18,7 @@ export function DataAttributes(): JSX.Element {
return (
<>
- Specify a comma-separated list of{' '}
+ Specify a list of{' '}
data attributes
{' '}
@@ -33,8 +33,9 @@ export function DataAttributes(): JSX.Element {
be button[data-custom-id='cta-button']
.
-
setValue(values || [])}
value={value}
data-attr="data-attribute-select"
diff --git a/frontend/src/scenes/settings/project/TimezoneConfig.tsx b/frontend/src/scenes/settings/project/TimezoneConfig.tsx
index f596f162115df..c02caa0ee0a03 100644
--- a/frontend/src/scenes/settings/project/TimezoneConfig.tsx
+++ b/frontend/src/scenes/settings/project/TimezoneConfig.tsx
@@ -1,6 +1,6 @@
import { useActions, useValues } from 'kea'
import { LemonDialog } from 'lib/lemon-ui/LemonDialog'
-import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
+import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect/LemonInputSelect'
import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { teamLogic } from 'scenes/teamLogic'
@@ -24,14 +24,14 @@ export function TimezoneConfig(): JSX.Element {
}))
return (
-
-
+ {
+ onChange={([newTimezone]): void => {
// This is a string for a single-mode select, but typing is poor
if (!preflight?.available_timezones) {
throw new Error('No timezones are available')
diff --git a/frontend/src/scenes/settings/user/PersonalAPIKeys.tsx b/frontend/src/scenes/settings/user/PersonalAPIKeys.tsx
index 448e485233f4d..af63bd46dd23b 100644
--- a/frontend/src/scenes/settings/user/PersonalAPIKeys.tsx
+++ b/frontend/src/scenes/settings/user/PersonalAPIKeys.tsx
@@ -4,12 +4,12 @@ import {
LemonBanner,
LemonDialog,
LemonInput,
+ LemonInputSelect,
LemonLabel,
LemonMenu,
LemonModal,
LemonSegmentedButton,
LemonSelect,
- LemonSelectMultiple,
LemonTable,
LemonTag,
Link,
@@ -111,7 +111,7 @@ function EditKeyModal(): JSX.Element {
- This API key will only allow access to selected projects.
{({ value, onChange }) => (
- String(x))}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 9a9535ab5d6a0..1193a25f62a08 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -43,6 +43,7 @@ import { NodeKind } from './queries/schema'
export type Optional = Omit & { [K in keyof T]?: T[K] }
// Keep this in sync with backend constants/features/{product_name}.yml
+
export enum AvailableFeature {
APPS = 'apps',
SLACK_INTEGRATION = 'slack_integration',
@@ -143,6 +144,7 @@ export enum AvailableFeature {
PRODUCT_ANALYTICS_SQL_QUERIES = 'product_analytics_sql_queries',
TWOFA_ENFORCEMENT = '2fa_enforcement',
AUDIT_LOGS = 'audit_logs',
+ PRIORITY_SUPPORT = 'priority_support',
}
type AvailableFeatureUnion = `${AvailableFeature}`
diff --git a/package.json b/package.json
index 90694ca67d6d6..f422e9835a6e9 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
},
"dependencies": {
"@ant-design/icons": "^4.7.0",
+ "@babel/runtime": "^7.24.0",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
@@ -96,6 +97,7 @@
"@types/react-transition-group": "^4.4.5",
"@types/react-virtualized": "^9.21.23",
"ajv": "^8.12.0",
+ "algoliasearch": "^4.22.1",
"antd": "^4.17.1",
"antd-dayjs-webpack-plugin": "^1.0.6",
"autoprefixer": "^10.4.13",
@@ -142,7 +144,7 @@
"pmtiles": "^2.11.0",
"postcss": "^8.4.31",
"postcss-preset-env": "^9.3.0",
- "posthog-js": "1.115.2",
+ "posthog-js": "1.116.1",
"posthog-js-lite": "2.5.0",
"prettier": "^2.8.8",
"prop-types": "^15.7.2",
@@ -154,6 +156,7 @@
"react-dom": "^18.2.0",
"react-draggable": "^4.2.0",
"react-grid-layout": "^1.3.0",
+ "react-instantsearch": "^7.6.0",
"react-intersection-observer": "^9.5.3",
"react-markdown": "^5.0.3",
"react-modal": "^3.15.1",
@@ -182,7 +185,6 @@
"@babel/preset-env": "^7.22.10",
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
- "@babel/runtime": "^7.22.10",
"@cypress/webpack-preprocessor": "^5.17.1",
"@playwright/test": "1.41.2",
"@sentry/types": "7.22.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 78423f993f9b0..ca7351e1a360f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -16,6 +16,9 @@ dependencies:
'@ant-design/icons':
specifier: ^4.7.0
version: 4.7.0(react-dom@18.2.0)(react@18.2.0)
+ '@babel/runtime':
+ specifier: ^7.24.0
+ version: 7.24.0
'@dnd-kit/core':
specifier: ^6.0.8
version: 6.0.8(react-dom@18.2.0)(react@18.2.0)
@@ -106,6 +109,9 @@ dependencies:
ajv:
specifier: ^8.12.0
version: 8.12.0
+ algoliasearch:
+ specifier: ^4.22.1
+ version: 4.22.1
antd:
specifier: ^4.17.1
version: 4.17.1(react-dom@18.2.0)(react@18.2.0)
@@ -245,8 +251,8 @@ dependencies:
specifier: ^9.3.0
version: 9.3.0(postcss@8.4.31)
posthog-js:
- specifier: 1.115.2
- version: 1.115.2
+ specifier: 1.116.1
+ version: 1.116.1
posthog-js-lite:
specifier: 2.5.0
version: 2.5.0
@@ -280,6 +286,9 @@ dependencies:
react-grid-layout:
specifier: ^1.3.0
version: 1.3.4(react-dom@18.2.0)(react@18.2.0)
+ react-instantsearch:
+ specifier: ^7.6.0
+ version: 7.6.0(algoliasearch@4.22.1)(react-dom@18.2.0)(react@18.2.0)
react-intersection-observer:
specifier: ^9.5.3
version: 9.5.3(react@18.2.0)
@@ -365,9 +374,6 @@ devDependencies:
'@babel/preset-typescript':
specifier: ^7.22.5
version: 7.22.5(@babel/core@7.22.10)
- '@babel/runtime':
- specifier: ^7.22.10
- version: 7.22.10
'@cypress/webpack-preprocessor':
specifier: ^5.17.1
version: 5.17.1(@babel/core@7.22.10)(@babel/preset-env@7.22.10)(babel-loader@8.3.0)(webpack@5.88.2)
@@ -704,6 +710,100 @@ packages:
resolution: {integrity: sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==}
dev: true
+ /@algolia/cache-browser-local-storage@4.22.1:
+ resolution: {integrity: sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==}
+ dependencies:
+ '@algolia/cache-common': 4.22.1
+ dev: false
+
+ /@algolia/cache-common@4.22.1:
+ resolution: {integrity: sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==}
+ dev: false
+
+ /@algolia/cache-in-memory@4.22.1:
+ resolution: {integrity: sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==}
+ dependencies:
+ '@algolia/cache-common': 4.22.1
+ dev: false
+
+ /@algolia/client-account@4.22.1:
+ resolution: {integrity: sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==}
+ dependencies:
+ '@algolia/client-common': 4.22.1
+ '@algolia/client-search': 4.22.1
+ '@algolia/transporter': 4.22.1
+ dev: false
+
+ /@algolia/client-analytics@4.22.1:
+ resolution: {integrity: sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==}
+ dependencies:
+ '@algolia/client-common': 4.22.1
+ '@algolia/client-search': 4.22.1
+ '@algolia/requester-common': 4.22.1
+ '@algolia/transporter': 4.22.1
+ dev: false
+
+ /@algolia/client-common@4.22.1:
+ resolution: {integrity: sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==}
+ dependencies:
+ '@algolia/requester-common': 4.22.1
+ '@algolia/transporter': 4.22.1
+ dev: false
+
+ /@algolia/client-personalization@4.22.1:
+ resolution: {integrity: sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==}
+ dependencies:
+ '@algolia/client-common': 4.22.1
+ '@algolia/requester-common': 4.22.1
+ '@algolia/transporter': 4.22.1
+ dev: false
+
+ /@algolia/client-search@4.22.1:
+ resolution: {integrity: sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==}
+ dependencies:
+ '@algolia/client-common': 4.22.1
+ '@algolia/requester-common': 4.22.1
+ '@algolia/transporter': 4.22.1
+ dev: false
+
+ /@algolia/events@4.0.1:
+ resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==}
+ dev: false
+
+ /@algolia/logger-common@4.22.1:
+ resolution: {integrity: sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==}
+ dev: false
+
+ /@algolia/logger-console@4.22.1:
+ resolution: {integrity: sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==}
+ dependencies:
+ '@algolia/logger-common': 4.22.1
+ dev: false
+
+ /@algolia/requester-browser-xhr@4.22.1:
+ resolution: {integrity: sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==}
+ dependencies:
+ '@algolia/requester-common': 4.22.1
+ dev: false
+
+ /@algolia/requester-common@4.22.1:
+ resolution: {integrity: sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==}
+ dev: false
+
+ /@algolia/requester-node-http@4.22.1:
+ resolution: {integrity: sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==}
+ dependencies:
+ '@algolia/requester-common': 4.22.1
+ dev: false
+
+ /@algolia/transporter@4.22.1:
+ resolution: {integrity: sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==}
+ dependencies:
+ '@algolia/cache-common': 4.22.1
+ '@algolia/logger-common': 4.22.1
+ '@algolia/requester-common': 4.22.1
+ dev: false
+
/@alloc/quick-lru@5.2.0:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
@@ -735,7 +835,7 @@ packages:
dependencies:
'@ant-design/colors': 6.0.0
'@ant-design/icons-svg': 4.2.1
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -747,7 +847,7 @@ packages:
peerDependencies:
react: '>=16.0.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
json2mq: 0.2.0
lodash: 4.17.21
@@ -3342,8 +3442,8 @@ packages:
resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==}
dev: true
- /@babel/runtime@7.22.10:
- resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==}
+ /@babel/runtime@7.24.0:
+ resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.14.0
@@ -5099,13 +5199,13 @@ packages:
/@radix-ui/number@1.0.1:
resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: true
/@radix-ui/primitive@1.0.1:
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: true
/@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0):
@@ -5121,7 +5221,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 17.0.52
'@types/react-dom': 18.2.14
@@ -5142,7 +5242,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
@@ -5162,7 +5262,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
dev: true
@@ -5176,7 +5276,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
dev: true
@@ -5190,7 +5290,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
dev: true
@@ -5208,7 +5308,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
@@ -5229,7 +5329,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
dev: true
@@ -5247,7 +5347,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.52)(react@18.2.0)
@@ -5266,7 +5366,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@types/react': 17.0.52
react: 18.2.0
@@ -5285,7 +5385,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@floating-ui/react-dom': 2.0.1(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.52)(react@18.2.0)
@@ -5315,7 +5415,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 17.0.52
'@types/react-dom': 18.2.14
@@ -5336,7 +5436,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-slot': 1.0.2(@types/react@17.0.52)(react@18.2.0)
'@types/react': 17.0.52
'@types/react-dom': 18.2.14
@@ -5357,7 +5457,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.52)(react@18.2.0)
@@ -5386,7 +5486,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/number': 1.0.1
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
@@ -5427,7 +5527,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 17.0.52
'@types/react-dom': 18.2.14
@@ -5444,7 +5544,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@types/react': 17.0.52
react: 18.2.0
@@ -5463,7 +5563,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-context': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@17.0.52)(react@18.2.0)
@@ -5490,7 +5590,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.52)(react@18.2.0)
@@ -5513,7 +5613,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-context': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@17.0.52)(react@18.2.0)
@@ -5536,7 +5636,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
dev: true
@@ -5550,7 +5650,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@types/react': 17.0.52
react: 18.2.0
@@ -5565,7 +5665,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@types/react': 17.0.52
react: 18.2.0
@@ -5580,7 +5680,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
dev: true
@@ -5594,7 +5694,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
dev: true
@@ -5608,7 +5708,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/rect': 1.0.1
'@types/react': 17.0.52
react: 18.2.0
@@ -5623,7 +5723,7 @@ packages:
'@types/react':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.52)(react@18.2.0)
'@types/react': 17.0.52
react: 18.2.0
@@ -5642,7 +5742,7 @@ packages:
'@types/react-dom':
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.14)(@types/react@17.0.52)(react-dom@18.2.0)(react@18.2.0)
'@types/react': 17.0.52
'@types/react-dom': 18.2.14
@@ -5653,7 +5753,7 @@ packages:
/@radix-ui/rect@1.0.1:
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: true
/@react-hook/latest@1.0.3(react@18.2.0):
@@ -5745,13 +5845,13 @@ packages:
/@remirror/core-constants@2.0.0:
resolution: {integrity: sha512-vpePPMecHJllBqCWXl6+FIcZqS+tRUM2kSCCKFeEo1H3XUEv3ocijBIPhnlSAa7g6maX+12ATTgxrOsLpWVr2g==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: false
/@remirror/core-helpers@2.0.1:
resolution: {integrity: sha512-s8M1pn33aBUhduvD1QR02uUQMegnFkGaTr4c1iBzxTTyg0rbQstzuQ7Q8TkL6n64JtgCdJS9jLz2dONb2meBKQ==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@linaria/core': 3.0.0-beta.13
'@remirror/core-constants': 2.0.0
'@remirror/types': 1.0.0
@@ -6172,7 +6272,7 @@ packages:
'@storybook/client-logger': 7.5.1
'@storybook/core-events': 7.5.1
'@storybook/global': 5.0.0
- qs: 6.11.2
+ qs: 6.12.0
telejson: 7.2.0
tiny-invariant: 1.3.1
dev: true
@@ -6194,7 +6294,7 @@ packages:
'@storybook/client-logger': 7.6.3
'@storybook/core-events': 7.6.3
'@storybook/global': 5.0.0
- qs: 6.11.2
+ qs: 6.12.0
telejson: 7.2.0
tiny-invariant: 1.3.1
dev: true
@@ -6540,6 +6640,12 @@ packages:
type-fest: 2.19.0
dev: true
+ /@storybook/csf@0.1.3:
+ resolution: {integrity: sha512-IPZvXXo4b3G+gpmgBSBqVM81jbp2ePOKsvhgJdhyZJtkYQCII7rg9KKLQhvBQM5sLaF1eU6r0iuwmyynC9d9SA==}
+ dependencies:
+ type-fest: 2.19.0
+ dev: true
+
/@storybook/docs-mdx@0.1.0:
resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==}
dev: true
@@ -6569,7 +6675,7 @@ packages:
'@storybook/channels': 7.6.17
'@storybook/client-logger': 7.6.17
'@storybook/core-events': 7.6.17
- '@storybook/csf': 0.1.2
+ '@storybook/csf': 0.1.3
'@storybook/global': 5.0.0
'@storybook/router': 7.6.17
'@storybook/theming': 7.6.17(react-dom@18.2.0)(react@18.2.0)
@@ -6684,7 +6790,7 @@ packages:
'@storybook/channels': 7.6.17
'@storybook/client-logger': 7.6.17
'@storybook/core-events': 7.6.17
- '@storybook/csf': 0.1.2
+ '@storybook/csf': 0.1.3
'@storybook/global': 5.0.0
'@storybook/types': 7.6.17
'@types/qs': 6.9.12
@@ -7140,7 +7246,7 @@ packages:
engines: {node: '>=12'}
dependencies:
'@babel/code-frame': 7.22.10
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/aria-query': 4.2.2
aria-query: 5.1.3
chalk: 4.1.2
@@ -7153,7 +7259,7 @@ packages:
engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
dependencies:
'@adobe/css-tools': 4.0.1
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/testing-library__jest-dom': 5.14.5
aria-query: 5.1.3
chalk: 3.0.0
@@ -7179,7 +7285,7 @@ packages:
react-test-renderer:
optional: true
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@types/react': 17.0.52
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -7193,7 +7299,7 @@ packages:
react: ^18.0.0
react-dom: ^18.0.0
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@testing-library/dom': 8.19.0
'@types/react-dom': 18.2.14
react: 18.2.0
@@ -7206,7 +7312,7 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@testing-library/dom': 8.19.0
dev: true
@@ -7807,6 +7913,10 @@ packages:
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
dev: true
+ /@types/dom-speech-recognition@0.0.1:
+ resolution: {integrity: sha512-udCxb8DvjcDKfk1WTBzDsxFbLgYxmQGKrE/ricoMqHRNjSlSUCcamVTA5lIQqzY10mY5qCY0QDwBfFEwhfoDPw==}
+ dev: false
+
/@types/dompurify@3.0.3:
resolution: {integrity: sha512-odiGr/9/qMqjcBOe5UhcNLOFHSYmKFOyr+bJ/Xu3Qp4k1pNPAlNLUVNNLcLfjQI7+W7ObX58EdD3H+3p3voOvA==}
dependencies:
@@ -7877,6 +7987,10 @@ packages:
resolution: {integrity: sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA==}
dev: false
+ /@types/google.maps@3.55.4:
+ resolution: {integrity: sha512-Ip3IfRs3RZjeC88V8FGnWQTQXeS5gkJedPSosN6DMi9Xs8buGTpsPq6UhREoZsGH+62VoQ6jiRBUR8R77If69w==}
+ dev: false
+
/@types/graceful-fs@4.1.5:
resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==}
dependencies:
@@ -7889,6 +8003,10 @@ packages:
'@types/unist': 2.0.6
dev: false
+ /@types/hogan.js@3.0.5:
+ resolution: {integrity: sha512-/uRaY3HGPWyLqOyhgvW9Aa43BNnLZrNeQxl2p8wqId4UHMfPKolSB+U7BlZyO1ng7MkLnyEAItsBzCG0SDhqrA==}
+ dev: false
+
/@types/html-minifier-terser@6.1.0:
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
dev: true
@@ -8077,7 +8195,6 @@ packages:
/@types/qs@6.9.12:
resolution: {integrity: sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==}
- dev: true
/@types/query-selector-shadow-dom@1.0.0:
resolution: {integrity: sha512-cTGo8ZxW0WXFDV7gvL/XCq4213t6S/yWaSGqscnXUTNDWqwnsYKegB/VAzQDwzmACoLzIbGbYXdjJOgfPLu7Ig==}
@@ -8640,6 +8757,10 @@ packages:
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
dev: true
+ /abbrev@1.1.1:
+ resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+ dev: false
+
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@@ -8770,6 +8891,34 @@ packages:
require-from-string: 2.0.2
uri-js: 4.4.1
+ /algoliasearch-helper@3.16.2(algoliasearch@4.22.1):
+ resolution: {integrity: sha512-Yl/Gu5Cq4Z5s/AJ0jR37OPI1H3+z7PHz657ibyaXgMOaWvPlZ3OACN13N+7HCLPUlB0BN+8BtmrG/CqTilowBA==}
+ peerDependencies:
+ algoliasearch: '>= 3.1 < 6'
+ dependencies:
+ '@algolia/events': 4.0.1
+ algoliasearch: 4.22.1
+ dev: false
+
+ /algoliasearch@4.22.1:
+ resolution: {integrity: sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==}
+ dependencies:
+ '@algolia/cache-browser-local-storage': 4.22.1
+ '@algolia/cache-common': 4.22.1
+ '@algolia/cache-in-memory': 4.22.1
+ '@algolia/client-account': 4.22.1
+ '@algolia/client-analytics': 4.22.1
+ '@algolia/client-common': 4.22.1
+ '@algolia/client-personalization': 4.22.1
+ '@algolia/client-search': 4.22.1
+ '@algolia/logger-common': 4.22.1
+ '@algolia/logger-console': 4.22.1
+ '@algolia/requester-browser-xhr': 4.22.1
+ '@algolia/requester-common': 4.22.1
+ '@algolia/requester-node-http': 4.22.1
+ '@algolia/transporter': 4.22.1
+ dev: false
+
/ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@@ -8847,7 +8996,7 @@ packages:
'@ant-design/colors': 6.0.0
'@ant-design/icons': 4.7.0(react-dom@18.2.0)(react@18.2.0)
'@ant-design/react-slick': 0.28.1(react@18.2.0)
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@ctrl/tinycolor': 3.4.1
array-tree-filter: 2.1.0
classnames: 2.3.2
@@ -9640,7 +9789,6 @@ packages:
function-bind: 1.1.2
get-intrinsic: 1.2.4
set-function-length: 1.2.2
- dev: true
/callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
@@ -11034,7 +11182,6 @@ packages:
es-define-property: 1.0.0
es-errors: 1.3.0
gopd: 1.0.1
- dev: true
/define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
@@ -11195,7 +11342,7 @@ packages:
/dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
csstype: 3.1.1
dev: false
@@ -11519,12 +11666,10 @@ packages:
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.2.4
- dev: true
/es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
- dev: true
/es-get-iterator@1.1.2:
resolution: {integrity: sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==}
@@ -12752,7 +12897,6 @@ packages:
has-proto: 1.0.3
has-symbols: 1.0.3
hasown: 2.0.2
- dev: true
/get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
@@ -13050,7 +13194,6 @@ packages:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
dependencies:
es-define-property: 1.0.0
- dev: true
/has-proto@1.0.1:
resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
@@ -13059,7 +13202,6 @@ packages:
/has-proto@1.0.3:
resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
engines: {node: '>= 0.4'}
- dev: true
/has-symbols@1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
@@ -13096,7 +13238,6 @@ packages:
engines: {node: '>= 0.4'}
dependencies:
function-bind: 1.1.2
- dev: true
/hast-util-parse-selector@2.2.5:
resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==}
@@ -13133,9 +13274,17 @@ packages:
/history@5.3.0:
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: true
+ /hogan.js@3.0.2:
+ resolution: {integrity: sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==}
+ hasBin: true
+ dependencies:
+ mkdirp: 0.3.0
+ nopt: 1.0.10
+ dev: false
+
/hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
@@ -13160,6 +13309,10 @@ packages:
lru-cache: 6.0.0
dev: true
+ /htm@3.1.1:
+ resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==}
+ dev: false
+
/html-encoding-sniffer@3.0.0:
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
engines: {node: '>=12'}
@@ -13453,6 +13606,30 @@ packages:
wrap-ansi: 7.0.0
dev: true
+ /instantsearch-ui-components@0.3.0:
+ resolution: {integrity: sha512-PCVvw9L0YHZs99ZZNRzmF4ghre6SVq2tiz7yCPIamMR+2pccpFXwtdJ2Gmdg+FF4SLif4d8TldvxWFOB0+L5gg==}
+ dev: false
+
+ /instantsearch.js@4.65.0(algoliasearch@4.22.1):
+ resolution: {integrity: sha512-LCJErlVwmsh/41CiEJRcoVPxfa+06yb1qmZfcvzXOMwC6ydb/yfBlFxQgjsQKYA2adwH40c3YF34Jq+V5YiaMg==}
+ peerDependencies:
+ algoliasearch: '>= 3.1 < 6'
+ dependencies:
+ '@algolia/events': 4.0.1
+ '@types/dom-speech-recognition': 0.0.1
+ '@types/google.maps': 3.55.4
+ '@types/hogan.js': 3.0.5
+ '@types/qs': 6.9.12
+ algoliasearch: 4.22.1
+ algoliasearch-helper: 3.16.2(algoliasearch@4.22.1)
+ hogan.js: 3.0.2
+ htm: 3.1.1
+ instantsearch-ui-components: 0.3.0
+ preact: 10.19.6
+ qs: 6.9.7
+ search-insights: 2.13.0
+ dev: false
+
/internal-slot@1.0.3:
resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
engines: {node: '>= 0.4'}
@@ -13558,7 +13735,7 @@ packages:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
dependencies:
- call-bind: 1.0.5
+ call-bind: 1.0.7
has-tostringtag: 1.0.0
/is-buffer@1.1.6:
@@ -13625,7 +13802,7 @@ packages:
/is-finalizationregistry@1.0.2:
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
dependencies:
- call-bind: 1.0.5
+ call-bind: 1.0.7
dev: true
/is-fullwidth-code-point@2.0.0:
@@ -13824,8 +14001,8 @@ packages:
/is-weakset@2.0.2:
resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==}
dependencies:
- call-bind: 1.0.5
- get-intrinsic: 1.2.2
+ call-bind: 1.0.7
+ get-intrinsic: 1.2.4
/is-what@3.14.1:
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
@@ -15598,6 +15775,11 @@ packages:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
dev: true
+ /mkdirp@0.3.0:
+ resolution: {integrity: sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==}
+ deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
+ dev: false
+
/mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
@@ -15799,6 +15981,13 @@ packages:
/node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+ /nopt@1.0.10:
+ resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==}
+ hasBin: true
+ dependencies:
+ abbrev: 1.1.1
+ dev: false
+
/normalize-package-data@2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
@@ -16431,7 +16620,7 @@ packages:
resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==}
engines: {node: '>=10'}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: true
/postcss-attribute-case-insensitive@6.0.2(postcss@8.4.31):
@@ -17252,8 +17441,8 @@ packages:
resolution: {integrity: sha512-Urvlp0Vu9h3td0BVFWt0QXFJDoOZcaAD83XM9d91NKMKTVPZtfU0ysoxstIf5mw/ce9ZfuMgpWPaagrZI4rmSg==}
dev: false
- /posthog-js@1.115.2:
- resolution: {integrity: sha512-nGTxDjH8df0FTd1plIqKFsmSynkkI/LmvYlJP7sqeKvtXhcQpVi4+avMhNWIasoWvyQbp65hmvwXyXyQ7jk2cw==}
+ /posthog-js@1.116.1:
+ resolution: {integrity: sha512-tYKw6K23S3koa2sfX0sylno7jQQ6ET7u1Lw4KqowhciNhS0R5OWTo3HWEJPt64e9IzeWQGcgb9utJIWwrp5D0Q==}
dependencies:
fflate: 0.4.8
preact: 10.19.6
@@ -17498,7 +17687,7 @@ packages:
prosemirror-state: ^1
prosemirror-view: ^1
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
'@remirror/core-constants': 2.0.0
'@remirror/core-helpers': 2.0.1
escape-string-regexp: 4.0.0
@@ -17638,6 +17827,11 @@ packages:
side-channel: 1.0.6
dev: true
+ /qs@6.9.7:
+ resolution: {integrity: sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==}
+ engines: {node: '>=0.6'}
+ dev: false
+
/query-selector-shadow-dom@1.0.0:
resolution: {integrity: sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==}
dev: false
@@ -17697,7 +17891,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
dom-align: 1.12.3
lodash: 4.17.21
@@ -17713,7 +17907,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
array-tree-filter: 2.1.0
rc-tree-select: 4.6.3(react-dom@18.2.0)(react@18.2.0)
rc-trigger: 5.3.3(react-dom@18.2.0)(react@18.2.0)
@@ -17729,7 +17923,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -17741,7 +17935,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -17756,7 +17950,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -17770,7 +17964,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -17783,7 +17977,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-trigger: 5.3.3(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -17797,7 +17991,7 @@ packages:
react: '>= 16.9.0'
react-dom: '>= 16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
async-validator: 4.2.5
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -17810,7 +18004,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-dialog: 8.6.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -17824,7 +18018,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -17837,7 +18031,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-menu: 9.0.14(react-dom@18.2.0)(react@18.2.0)
rc-textarea: 0.3.4(react-dom@18.2.0)(react@18.2.0)
@@ -17853,7 +18047,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
rc-overflow: 1.2.8(react-dom@18.2.0)(react@18.2.0)
@@ -17870,7 +18064,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -17884,7 +18078,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -17898,7 +18092,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-resize-observer: 1.2.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -17912,7 +18106,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -17925,7 +18119,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
date-fns: 2.29.3
dayjs: 1.11.6
@@ -17943,7 +18137,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -17956,7 +18150,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -17969,7 +18163,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -17984,7 +18178,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
rc-overflow: 1.2.8(react-dom@18.2.0)(react@18.2.0)
@@ -18002,7 +18196,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-tooltip: 5.2.2(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -18018,7 +18212,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -18031,7 +18225,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -18045,7 +18239,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-resize-observer: 1.2.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -18061,7 +18255,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-dropdown: 3.2.0(react-dom@18.2.0)(react@18.2.0)
rc-menu: 9.0.14(react-dom@18.2.0)(react@18.2.0)
@@ -18077,7 +18271,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-resize-observer: 1.2.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -18091,7 +18285,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
rc-trigger: 5.3.3(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -18103,7 +18297,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-trigger: 5.3.3(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -18116,7 +18310,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-select: 13.1.1(react-dom@18.2.0)(react@18.2.0)
rc-tree: 5.2.2(react-dom@18.2.0)(react@18.2.0)
@@ -18132,7 +18326,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -18148,7 +18342,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-align: 4.0.12(react-dom@18.2.0)(react@18.2.0)
rc-motion: 2.6.2(react-dom@18.2.0)(react@18.2.0)
@@ -18163,7 +18357,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
@@ -18176,7 +18370,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-is: 16.13.1
@@ -18190,7 +18384,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
classnames: 2.3.2
rc-resize-observer: 1.2.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.24.4(react-dom@18.2.0)(react@18.2.0)
@@ -18283,7 +18477,7 @@ packages:
peerDependencies:
react: '>=16.13.1'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
react: 18.2.0
dev: true
@@ -18302,6 +18496,36 @@ packages:
react-resizable: 3.0.5(react-dom@18.2.0)(react@18.2.0)
dev: false
+ /react-instantsearch-core@7.6.0(algoliasearch@4.22.1)(react@18.2.0):
+ resolution: {integrity: sha512-FBTwAJAmNSha6pSFOP1fTPjIbvyv5btS49SsdWPvQ981yiMD+zWtvCXZlVTxrBGVH6mYGbmBT0lCHTOm4kpdOg==}
+ peerDependencies:
+ algoliasearch: '>= 3.1 < 5'
+ react: '>= 16.8.0 < 19'
+ dependencies:
+ '@babel/runtime': 7.24.0
+ algoliasearch: 4.22.1
+ algoliasearch-helper: 3.16.2(algoliasearch@4.22.1)
+ instantsearch.js: 4.65.0(algoliasearch@4.22.1)
+ react: 18.2.0
+ use-sync-external-store: 1.2.0(react@18.2.0)
+ dev: false
+
+ /react-instantsearch@7.6.0(algoliasearch@4.22.1)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-9bvIkVeHUK4vaKdFqJZFbI3+1hmYDKWG52RJe+OxfLPvu4EkiSsnIc8qf3A0q0GnIdb0+HUIeZRBkUt/vYYCbQ==}
+ peerDependencies:
+ algoliasearch: '>= 3.1 < 5'
+ react: '>= 16.8.0 < 19'
+ react-dom: '>= 16.8.0 < 19'
+ dependencies:
+ '@babel/runtime': 7.24.0
+ algoliasearch: 4.22.1
+ instantsearch-ui-components: 0.3.0
+ instantsearch.js: 4.65.0(algoliasearch@4.22.1)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-instantsearch-core: 7.6.0(algoliasearch@4.22.1)(react@18.2.0)
+ dev: false
+
/react-intersection-observer@9.5.3(react@18.2.0):
resolution: {integrity: sha512-NJzagSdUPS5rPhaLsHXYeJbsvdpbJwL6yCHtMk91hc0ufQ2BnXis+0QQ9NBh6n9n+Q3OyjR6OQLShYbaNBkThQ==}
peerDependencies:
@@ -18452,7 +18676,7 @@ packages:
peerDependencies:
react: '>= 0.14.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
highlight.js: 10.7.3
lowlight: 1.20.0
prismjs: 1.29.0
@@ -18466,7 +18690,7 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
react: 18.2.0
use-composed-ref: 1.3.0(react@18.2.0)
use-latest: 1.2.1(@types/react@17.0.52)(react@18.2.0)
@@ -18491,7 +18715,7 @@ packages:
react: '>=16.6.0'
react-dom: '>=16.6.0'
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
@@ -18505,7 +18729,7 @@ packages:
react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0
react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
clsx: 1.2.1
dom-helpers: 5.2.1
loose-envify: 1.4.0
@@ -18649,7 +18873,7 @@ packages:
/redux@4.2.0:
resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: false
/reflect.getprototypeof@1.0.4:
@@ -18689,7 +18913,7 @@ packages:
/regenerator-transform@0.15.2:
resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==}
dependencies:
- '@babel/runtime': 7.22.10
+ '@babel/runtime': 7.24.0
dev: true
/regexp.prototype.flags@1.4.3:
@@ -19270,6 +19494,10 @@ packages:
compute-scroll-into-view: 1.0.16
dev: false
+ /search-insights@2.13.0:
+ resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==}
+ dev: false
+
/semver-compare@1.0.0:
resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
dev: true
@@ -19354,7 +19582,6 @@ packages:
get-intrinsic: 1.2.4
gopd: 1.0.1
has-property-descriptors: 1.0.2
- dev: true
/set-function-name@2.0.1:
resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==}
diff --git a/posthog/api/capture.py b/posthog/api/capture.py
index 7b36b1ccece6a..9dfc61aa3979f 100644
--- a/posthog/api/capture.py
+++ b/posthog/api/capture.py
@@ -577,7 +577,7 @@ def capture_internal(event, distinct_id, ip, site_url, now, sent_at, event_uuid=
if (
distinct_id.lower() not in LIKELY_ANONYMOUS_IDS
- and is_randomly_partitioned(candidate_partition_key) is False
+ and not is_randomly_partitioned(candidate_partition_key)
or historical
):
kafka_partition_key = hashlib.sha256(candidate_partition_key.encode()).hexdigest()
@@ -613,7 +613,7 @@ def is_randomly_partitioned(candidate_partition_key: str) -> bool:
if settings.PARTITION_KEY_AUTOMATIC_OVERRIDE_ENABLED:
has_capacity = LIMITER.consume(candidate_partition_key)
- if has_capacity is False:
+ if not has_capacity:
if not LOG_RATE_LIMITER.consume(candidate_partition_key):
# Return early if we have logged this key already.
return True
diff --git a/posthog/temporal/data_imports/__init__.py b/posthog/temporal/data_imports/__init__.py
index 35e20f0ffc50e..c6a142c712d39 100644
--- a/posthog/temporal/data_imports/__init__.py
+++ b/posthog/temporal/data_imports/__init__.py
@@ -4,6 +4,7 @@
update_external_data_job_model,
run_external_data_job,
validate_schema_activity,
+ create_source_templates,
)
WORKFLOWS = [ExternalDataJobWorkflow]
@@ -13,4 +14,5 @@
update_external_data_job_model,
run_external_data_job,
validate_schema_activity,
+ create_source_templates,
]
diff --git a/posthog/temporal/data_imports/external_data_job.py b/posthog/temporal/data_imports/external_data_job.py
index 3288d9a313c9d..db99eeb1de315 100644
--- a/posthog/temporal/data_imports/external_data_job.py
+++ b/posthog/temporal/data_imports/external_data_job.py
@@ -10,6 +10,7 @@
# TODO: remove dependency
from posthog.temporal.batch_exports.base import PostHogWorkflow
+from posthog.warehouse.data_load.source_templates import create_warehouse_templates_for_source
from posthog.warehouse.data_load.validate_schema import validate_schema_and_update_table
from posthog.temporal.data_imports.pipelines.pipeline import DataImportPipeline, PipelineInputs
@@ -125,6 +126,17 @@ async def validate_schema_activity(inputs: ValidateSchemaInputs) -> None:
)
+@dataclasses.dataclass
+class CreateSourceTemplateInputs:
+ team_id: int
+ run_id: str
+
+
+@activity.defn
+async def create_source_templates(inputs: CreateSourceTemplateInputs) -> None:
+ await create_warehouse_templates_for_source(team_id=inputs.team_id, run_id=inputs.run_id)
+
+
@dataclasses.dataclass
class ExternalDataWorkflowInputs:
team_id: int
@@ -291,6 +303,14 @@ async def run(self, inputs: ExternalDataWorkflowInputs):
retry_policy=RetryPolicy(maximum_attempts=2),
)
+ # Create source templates
+ await workflow.execute_activity(
+ create_source_templates,
+ CreateSourceTemplateInputs(team_id=inputs.team_id, run_id=run_id),
+ start_to_close_timeout=dt.timedelta(minutes=10),
+ retry_policy=RetryPolicy(maximum_attempts=2),
+ )
+
except exceptions.ActivityError as e:
if isinstance(e.cause, exceptions.CancelledError):
update_inputs.status = ExternalDataJob.Status.CANCELLED
diff --git a/posthog/temporal/tests/external_data/test_external_data_job.py b/posthog/temporal/tests/external_data/test_external_data_job.py
index 9f0ca2d9a0d32..45de2c687cb7d 100644
--- a/posthog/temporal/tests/external_data/test_external_data_job.py
+++ b/posthog/temporal/tests/external_data/test_external_data_job.py
@@ -11,6 +11,7 @@
ValidateSchemaInputs,
create_external_data_job,
create_external_data_job_model,
+ create_source_templates,
run_external_data_job,
update_external_data_job_model,
validate_schema_activity,
@@ -781,6 +782,7 @@ async def test_external_data_job_workflow_blank(team, **kwargs):
update_external_data_job_model,
run_external_data_job,
validate_schema_activity,
+ create_source_templates,
],
workflow_runner=UnsandboxedWorkflowRunner(),
):
@@ -844,6 +846,7 @@ async def mock_async_func(inputs):
update_external_data_job_model,
run_external_data_job,
validate_schema_activity,
+ create_source_templates,
],
workflow_runner=UnsandboxedWorkflowRunner(),
):
diff --git a/posthog/warehouse/data_load/source_templates.py b/posthog/warehouse/data_load/source_templates.py
new file mode 100644
index 0000000000000..afc61cccd1be3
--- /dev/null
+++ b/posthog/warehouse/data_load/source_templates.py
@@ -0,0 +1,70 @@
+from posthog.temporal.common.logger import bind_temporal_worker_logger
+from posthog.warehouse.models.external_data_job import ExternalDataJob, get_external_data_job, get_latest_run_if_exists
+from posthog.warehouse.models.external_data_source import ExternalDataSource
+from posthog.warehouse.models.join import DataWarehouseJoin
+from posthog.warehouse.util import database_sync_to_async
+
+
+@database_sync_to_async
+def database_operations(team_id: int, table_prefix: str) -> None:
+ customer_join_exists = DataWarehouseJoin.objects.filter(
+ team_id=team_id,
+ source_table_name="persons",
+ source_table_key="properties.email",
+ joining_table_name=f"{table_prefix}stripe_customer",
+ joining_table_key="email",
+ field_name="stripe_customer",
+ ).exists()
+
+ invoice_join_exists = DataWarehouseJoin.objects.filter(
+ team_id=team_id,
+ source_table_name="persons",
+ source_table_key="properties.email",
+ joining_table_name=f"{table_prefix}stripe_invoice",
+ joining_table_key="customer_email",
+ field_name="stripe_invoice",
+ ).exists()
+
+ if not customer_join_exists:
+ DataWarehouseJoin.objects.create(
+ team_id=team_id,
+ source_table_name="persons",
+ source_table_key="properties.email",
+ joining_table_name=f"{table_prefix}stripe_customer",
+ joining_table_key="email",
+ field_name="stripe_customer",
+ )
+
+ if not invoice_join_exists:
+ DataWarehouseJoin.objects.create(
+ team_id=team_id,
+ source_table_name="persons",
+ source_table_key="properties.email",
+ joining_table_name=f"{table_prefix}stripe_invoice",
+ joining_table_key="customer_email",
+ field_name="stripe_invoice",
+ )
+
+
+async def create_warehouse_templates_for_source(team_id: int, run_id: str) -> None:
+ logger = await bind_temporal_worker_logger(team_id=team_id)
+
+ job: ExternalDataJob = await get_external_data_job(job_id=run_id)
+ last_successful_job: ExternalDataJob | None = await get_latest_run_if_exists(job.team_id, job.pipeline_id)
+
+ source: ExternalDataSource.Type = job.pipeline.source_type
+
+ # Quick exit if this isn't the first sync, or a stripe source
+ if source != ExternalDataSource.Type.STRIPE or last_successful_job is not None:
+ logger.info(
+ f"Create warehouse templates skipped for job {run_id}",
+ )
+ return
+
+ table_prefix = job.pipeline.prefix or ""
+
+ await database_operations(team_id, table_prefix)
+
+ logger.info(
+ f"Created warehouse template for job {run_id}",
+ )
diff --git a/tailwind.config.js b/tailwind.config.js
index 7dbeacca053db..e3d50a0fd39d1 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -7,6 +7,8 @@ const config = {
// TODO: Move all colors over to Tailwind
// Currently color utility classes are still generated with SCSS in colors.scss due to relying on our color
// CSS vars in lots of stylesheets
+
+ purple: '#B62AD9',
},
fontFamily: {
sans: [
diff --git a/unit.json b/unit.json
index 3982169eec719..72e3d2f03edb6 100644
--- a/unit.json
+++ b/unit.json
@@ -19,11 +19,7 @@
"posthog": [
{
"match": {
- "uri": [
- "/_health",
- "/_readyz",
- "/_livez"
- ]
+ "uri": ["/_health", "/_readyz", "/_livez"]
},
"action": {
"pass": "applications/posthog-health"