diff --git a/.github/workflows/ci-backend-depot.yml b/.github/workflows/ci-backend-depot.yml new file mode 100644 index 0000000000000..743fef1edaed1 --- /dev/null +++ b/.github/workflows/ci-backend-depot.yml @@ -0,0 +1,373 @@ +# This workflow runs all of our backend django tests. +# +# If these tests get too slow, look at increasing concurrency and re-timing the tests by manually dispatching +# .github/workflows/ci-backend-update-test-timing.yml action +name: Backend CI (depot) + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + inputs: + clickhouseServerVersion: + description: ClickHouse server version. Leave blank for default + type: string + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + # This is so that the workflow run isn't canceled when a snapshot update is pushed within it by posthog-bot + # We do however cancel from container-images-ci.yml if a commit is pushed by someone OTHER than posthog-bot + cancel-in-progress: false + +env: + SECRET_KEY: '6b01eee4f945ca25045b5aab440b953461faf08693a9abbf1166dc7c6b9772da' # unsafe - for testing only + DATABASE_URL: 'postgres://posthog:posthog@localhost:5432/posthog' + REDIS_URL: 'redis://localhost' + CLICKHOUSE_HOST: 'localhost' + CLICKHOUSE_SECURE: 'False' + CLICKHOUSE_VERIFY: 'False' + TEST: 1 + CLICKHOUSE_SERVER_IMAGE_VERSION: ${{ github.event.inputs.clickhouseServerVersion || '' }} + OBJECT_STORAGE_ENABLED: 'True' + OBJECT_STORAGE_ENDPOINT: 'http://localhost:19000' + OBJECT_STORAGE_ACCESS_KEY_ID: 'object_storage_root_user' + OBJECT_STORAGE_SECRET_ACCESS_KEY: 'object_storage_root_password' + +jobs: + # Job to decide if we should run backend ci + # See https://github.com/dorny/paths-filter#conditional-execution for more details + changes: + runs-on: depot-ubuntu-latest + timeout-minutes: 5 + if: github.repository == 'PostHog/posthog' + name: Determine need to run backend checks + # Set job outputs to values from filter step + outputs: + backend: ${{ steps.filter.outputs.backend }} + steps: + # For pull requests it's not necessary to checkout the code, but we + # also want this to run on master so we need to checkout + - uses: actions/checkout@v3 + + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + backend: + # Avoid running backend tests for irrelevant changes + # NOTE: we are at risk of missing a dependency here. We could make + # the dependencies more clear if we separated the backend/frontend + # code completely + # really we should ignore ee/frontend/** but dorny doesn't support that + # - '!ee/frontend/**' + # including the negated rule appears to work + # but makes it always match because the checked file always isn't `ee/frontend/**` 🙈 + - 'ee/**/*' + - 'posthog/**/*' + - 'bin/*.py' + - requirements.txt + - requirements-dev.txt + - mypy.ini + - pytest.ini + - frontend/src/queries/schema.json # Used for generating schema.py + - plugin-transpiler/src # Used for transpiling plugins + # Make sure we run if someone is explicitly change the workflow + - .github/workflows/ci-backend.yml + - .github/actions/run-backend-tests/action.yml + # We use docker compose for tests, make sure we rerun on + # changes to docker-compose.dev.yml e.g. dependency + # version changes + - docker-compose.dev.yml + - frontend/public/email/* + # These scripts are used in the CI + - bin/check_temporal_up + - bin/check_kafka_clickhouse_up + + backend-code-quality: + needs: changes + timeout-minutes: 30 + + name: Python code quality checks + runs-on: depot-ubuntu-latest + + steps: + # If this run wasn't initiated by the bot (meaning: snapshot update) and we've determined + # there are backend changes, cancel previous runs + - uses: n1hility/cancel-previous-runs@v3 + if: github.actor != 'posthog-bot' && needs.changes.outputs.backend == 'true' + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.10.10 + cache: 'pip' + cache-dependency-path: '**/requirements*.txt' + token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }} + + - uses: syphar/restore-virtualenv@v1 + id: cache-backend-tests + with: + custom_cache_key_element: v2- + + - name: Install SAML (python3-saml) dependencies + run: | + sudo apt-get update + sudo apt-get install libxml2-dev libxmlsec1 libxmlsec1-dev libxmlsec1-openssl + + - name: Install Python dependencies + if: steps.cache-backend-tests.outputs.cache-hit != 'true' + run: | + python -m pip install -r requirements.txt -r requirements-dev.txt + + - name: Check for syntax errors, import sort, and code style violations + run: | + ruff check . + + - name: Check formatting + run: | + ruff format --exclude posthog/hogql/grammar --check --diff . + + - name: Add Problem Matcher + run: echo "::add-matcher::.github/mypy-problem-matcher.json" + + - name: Check static typing + run: | + mypy -p posthog | mypy-baseline filter + + - name: Check if "schema.py" is up to date + run: | + npm run schema:build:python && git diff --exit-code + + - name: Check if ANTLR definitions are up to date + run: | + cd .. + sudo apt-get install default-jre + mkdir antlr + cd antlr + curl -o antlr.jar https://www.antlr.org/download/antlr-$ANTLR_VERSION-complete.jar + export PWD=`pwd` + echo '#!/bin/bash' > antlr + echo "java -jar $PWD/antlr.jar \$*" >> antlr + chmod +x antlr + export CLASSPATH=".:$PWD/antlr.jar:$CLASSPATH" + export PATH="$PWD:$PATH" + + cd ../posthog + antlr | grep "Version" + npm run grammar:build && git diff --exit-code + env: + # Installing a version of ANTLR compatible with what's in Homebrew as of October 2023 (version 4.13), + # as apt-get is quite out of date. The same version must be set in hogql_parser/pyproject.toml + ANTLR_VERSION: '4.13.1' + + check-migrations: + needs: changes + if: needs.changes.outputs.backend == 'true' + timeout-minutes: 10 + + name: Validate Django migrations + runs-on: depot-ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Stop/Start stack with Docker Compose + run: | + docker compose -f docker-compose.dev.yml down + docker compose -f docker-compose.dev.yml up -d + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.10.10 + cache: 'pip' + cache-dependency-path: '**/requirements*.txt' + token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }} + + - uses: syphar/restore-virtualenv@v1 + id: cache-backend-tests + with: + custom_cache_key_element: v1- + + - name: Install SAML (python3-saml) dependencies + run: | + sudo apt-get update + sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl + + - name: Install python dependencies + if: steps.cache-backend-tests.outputs.cache-hit != 'true' + run: | + python -m pip install -r requirements.txt -r requirements-dev.txt + + - uses: actions/checkout@v3 + with: + ref: master + + - name: Run migrations up to master + run: | + # We need to ensure we have requirements for the master branch + # now also, so we can run migrations up to master. + python -m pip install -r requirements.txt -r requirements-dev.txt + python manage.py migrate + + - uses: actions/checkout@v3 + + - name: Check migrations + run: | + python manage.py makemigrations --check --dry-run + git fetch origin master + # `git diff --name-only` returns a list of files that were changed - added OR deleted OR modified + # With `--name-status` we get the same, but including a column for status, respectively: A, D, M + # In this check we exclusively care about files that were + # added (A) in posthog/migrations/. We also want to ignore + # initial migrations (0001_*) as these are guaranteed to be + # run on initial setup where there is no data. + git diff --name-status origin/master..HEAD | grep "A\sposthog/migrations/" | awk '{print $2}' | grep -v migrations/0001_ | python manage.py test_migrations_are_safe + + django: + needs: changes + timeout-minutes: 30 + + name: Django tests – ${{ matrix.segment }} (persons-on-events ${{ matrix.person-on-events && 'on' || 'off' }}), Py ${{ matrix.python-version }}, ${{ matrix.clickhouse-server-image }} (${{matrix.group}}/${{ matrix.concurrency }}) + runs-on: depot-ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: ['3.10.10'] + clickhouse-server-image: ['clickhouse/clickhouse-server:23.11.2.11-alpine'] + segment: ['FOSS', 'EE'] + person-on-events: [false, true] + # :NOTE: Keep concurrency and groups in sync + concurrency: [5] + group: [1, 2, 3, 4, 5] + + steps: + # The first step is the only one that should run if `needs.changes.outputs.backend == 'false'`. + # All the other ones should rely on `needs.changes.outputs.backend` directly or indirectly, so that they're + # effectively skipped if backend code is unchanged. See https://github.com/PostHog/posthog/pull/15174. + - uses: actions/checkout@v3 + with: + fetch-depth: 1 + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + # Use PostHog Bot token when not on forks to enable proper snapshot updating + token: ${{ github.event.pull_request.head.repo.full_name == github.repository && secrets.POSTHOG_BOT_GITHUB_TOKEN || github.token }} + + - uses: ./.github/actions/run-backend-tests + if: needs.changes.outputs.backend == 'true' + with: + segment: ${{ matrix.segment }} + person-on-events: ${{ matrix.person-on-events }} + python-version: ${{ matrix.python-version }} + clickhouse-server-image: ${{ matrix.clickhouse-server-image }} + concurrency: ${{ matrix.concurrency }} + group: ${{ matrix.group }} + token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }} + + - uses: EndBug/add-and-commit@v9 + # Skip on forks + # Also skip for persons-on-events runs, as we want to ignore snapshots diverging there + if: ${{ github.repository == 'PostHog/posthog' && needs.changes.outputs.backend == 'true' && !matrix.person-on-events }} + with: + add: '["ee", "./**/*.ambr", "posthog/queries/", "posthog/migrations", "posthog/tasks", "posthog/hogql/"]' + message: 'Update query snapshots' + pull: --rebase --autostash # Make sure we're up-to-date with other segments' updates + default_author: github_actions + github_token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }} + + - name: Check if any snapshot changes were left uncomitted + id: changed-files + if: ${{ github.repository == 'PostHog/posthog' && needs.changes.outputs.backend == 'true' && !matrix.person-on-events }} + run: | + if [[ -z $(git status -s | grep -v ".test_durations" | tr -d "\n") ]] + then + echo 'files_found=false' >> $GITHUB_OUTPUT + else + echo 'diff=$(git status --porcelain)' >> $GITHUB_OUTPUT + echo 'files_found=true' >> $GITHUB_OUTPUT + fi + + - name: Fail CI if some snapshots have been updated but not committed + if: steps.changed-files.outputs.files_found == 'true' && steps.add-and-commit.outcome == 'success' + run: | + echo "${{ steps.changed-files.outputs.diff }}" + exit 1 + + - name: Archive email renders + uses: actions/upload-artifact@v3 + if: needs.changes.outputs.backend == 'true' && matrix.segment == 'FOSS' && matrix.person-on-events == false + with: + name: email_renders + path: posthog/tasks/test/__emails__ + retention-days: 5 + + async-migrations: + name: Async migrations tests - ${{ matrix.clickhouse-server-image }} + needs: changes + strategy: + fail-fast: false + matrix: + clickhouse-server-image: ['clickhouse/clickhouse-server:23.11.2.11-alpine'] + if: needs.changes.outputs.backend == 'true' + runs-on: depot-ubuntu-latest + steps: + - name: 'Checkout repo' + uses: actions/checkout@v3 + with: + fetch-depth: 1 + + - name: Start stack with Docker Compose + run: | + export CLICKHOUSE_SERVER_IMAGE_VERSION=${{ matrix.clickhouse-server-image }} + docker compose -f docker-compose.dev.yml down + docker compose -f docker-compose.dev.yml up -d + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.10.10 + cache: 'pip' + cache-dependency-path: '**/requirements*.txt' + token: ${{ secrets.POSTHOG_BOT_GITHUB_TOKEN }} + + - uses: syphar/restore-virtualenv@v1 + id: cache-backend-tests + with: + custom_cache_key_element: v2- + + - name: Install SAML (python3-saml) dependencies + run: | + sudo apt-get update + sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl + + - name: Install python dependencies + if: steps.cache-backend-tests.outputs.cache-hit != 'true' + shell: bash + run: | + python -m pip install -r requirements.txt -r requirements-dev.txt + + - name: Add kafka host to /etc/hosts for kafka connectivity + run: sudo echo "127.0.0.1 kafka" | sudo tee -a /etc/hosts + + - name: Set up needed files + run: | + mkdir -p frontend/dist + touch frontend/dist/index.html + touch frontend/dist/layout.html + touch frontend/dist/exporter.html + + - name: Wait for Clickhouse & Kafka + run: bin/check_kafka_clickhouse_up + + - name: Run async migrations tests + run: | + pytest -m "async_migrations" diff --git a/.github/workflows/ci-e2e-depot.yml b/.github/workflows/ci-e2e-depot.yml new file mode 100644 index 0000000000000..615238bf7d43b --- /dev/null +++ b/.github/workflows/ci-e2e-depot.yml @@ -0,0 +1,278 @@ +# +# This workflow runs CI E2E tests with Cypress. +# +# It relies on the container image built by 'container-images-ci.yml'. +# +name: E2E CI (depot) + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + changes: + runs-on: depot-ubuntu-latest + timeout-minutes: 5 + if: github.repository == 'PostHog/posthog' + name: Determine need to run E2E checks + # Set job outputs to values from filter step + outputs: + shouldTriggerCypress: ${{ steps.changes.outputs.shouldTriggerCypress }} + steps: + # For pull requests it's not necessary to check out the code + - uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + shouldTriggerCypress: + # Avoid running E2E tests for irrelevant changes + # NOTE: we are at risk of missing a dependency here. We could make + # the dependencies more clear if we separated the backend/frontend + # code completely + - 'ee/**' + - 'posthog/**' + - 'hogvm/**' + - 'bin/*' + - frontend/**/* + - requirements.txt + - requirements-dev.txt + - package.json + - pnpm-lock.yaml + # Make sure we run if someone is explicitly change the workflow + - .github/workflows/ci-e2e.yml + - .github/actions/build-n-cache-image/action.yml + # We use docker compose for tests, make sure we rerun on + # changes to docker-compose.dev.yml e.g. dependency + # version changes + - docker-compose.dev.yml + - Dockerfile + - cypress/** + + # Job that lists and chunks spec file names and caches node modules + chunks: + needs: changes + name: Cypress preparation + runs-on: depot-ubuntu-latest + timeout-minutes: 5 + outputs: + chunks: ${{ steps.chunk.outputs.chunks }} + + steps: + - name: Check out + uses: actions/checkout@v3 + + - name: Group spec files into chunks of three + id: chunk + run: echo "chunks=$(ls cypress/e2e/* | jq --slurp --raw-input -c 'split("\n")[:-1] | _nwise(2) | join("\n")' | jq --slurp -c .)" >> $GITHUB_OUTPUT + + container: + name: Build and cache container image + runs-on: depot-ubuntu-latest + timeout-minutes: 60 + needs: [changes] + permissions: + contents: read + id-token: write # allow issuing OIDC tokens for this workflow run + outputs: + tag: ${{ steps.build.outputs.tag }} + build-id: ${{ steps.build.outputs.build-id }} + steps: + - name: Checkout + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: actions/checkout@v3 + - name: Build the Docker image with Depot + if: needs.changes.outputs.shouldTriggerCypress == 'true' + # Build the container image in preparation for the E2E tests + uses: ./.github/actions/build-n-cache-image + id: build + with: + save: true + actions-id-token-request-url: ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL }} + + cypress: + name: Cypress E2E tests (${{ strategy.job-index }}) + runs-on: depot-ubuntu-latest + timeout-minutes: 60 + needs: [chunks, changes, container] + permissions: + id-token: write # allow issuing OIDC tokens for this workflow run + + strategy: + # when one test fails, DO NOT cancel the other + # containers, as there may be other spec failures + # we want to know about. + fail-fast: false + matrix: + chunk: ${{ fromJson(needs.chunks.outputs.chunks) }} + + steps: + - name: Checkout + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: actions/checkout@v3 + + - name: Install pnpm + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: pnpm/action-setup@v2 + with: + version: 8.x.x + + - name: Set up Node.js + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: actions/setup-node@v4 + with: + node-version: 18.12.1 + + - name: Get pnpm cache directory path + if: needs.changes.outputs.shouldTriggerCypress == 'true' + id: pnpm-cache-dir + run: echo "PNPM_STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Get cypress cache directory path + if: needs.changes.outputs.shouldTriggerCypress == 'true' + id: cypress-cache-dir + run: echo "CYPRESS_BIN_PATH=$(npx cypress cache path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + if: needs.changes.outputs.shouldTriggerCypress == 'true' + id: pnpm-cache + with: + path: | + ${{ steps.pnpm-cache-dir.outputs.PNPM_STORE_PATH }} + ${{ steps.cypress-cache-dir.outputs.CYPRESS_BIN_PATH }} + key: ${{ runner.os }}-pnpm-cypress-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-cypress- + + - name: Install package.json dependencies with pnpm + if: needs.changes.outputs.shouldTriggerCypress == 'true' + run: pnpm install --frozen-lockfile + + - name: Stop/Start stack with Docker Compose + # these are required checks so, we can't skip entire sections + if: needs.changes.outputs.shouldTriggerCypress == 'true' + run: | + docker compose -f docker-compose.dev.yml down + docker compose -f docker-compose.dev.yml up -d + + - name: Wait for ClickHouse + # these are required checks so, we can't skip entire sections + if: needs.changes.outputs.shouldTriggerCypress == 'true' + run: ./bin/check_kafka_clickhouse_up + + - name: Install Depot CLI + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: depot/setup-action@v1 + + - name: Get Docker image cached in Depot + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: depot/pull-action@v1 + with: + build-id: ${{ needs.container.outputs.build-id }} + tags: ${{ needs.container.outputs.tag }} + + - name: Write .env # This step intentionally has no if, so that GH always considers the action as having run + run: | + cat <> .env + SECRET_KEY=6b01eee4f945ca25045b5aab440b953461faf08693a9abbf1166dc7c6b9772da + REDIS_URL=redis://localhost + DATABASE_URL=postgres://posthog:posthog@localhost:5432/posthog + KAFKA_HOSTS=kafka:9092 + DISABLE_SECURE_SSL_REDIRECT=1 + SECURE_COOKIES=0 + OPT_OUT_CAPTURE=0 + E2E_TESTING=1 + SKIP_SERVICE_VERSION_REQUIREMENTS=1 + EMAIL_HOST=email.test.posthog.net + SITE_URL=http://localhost:8000 + NO_RESTART_LOOP=1 + CLICKHOUSE_SECURE=0 + OBJECT_STORAGE_ENABLED=1 + OBJECT_STORAGE_ENDPOINT=http://localhost:19000 + OBJECT_STORAGE_ACCESS_KEY_ID=object_storage_root_user + OBJECT_STORAGE_SECRET_ACCESS_KEY=object_storage_root_password + GITHUB_ACTION_RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + CELERY_METRICS_PORT=8999 + EOT + + - name: Start PostHog + # these are required checks so, we can't skip entire sections + if: needs.changes.outputs.shouldTriggerCypress == 'true' + run: | + mkdir -p /tmp/logs + + echo "Starting PostHog using the container image ${{ needs.container.outputs.tag }}" + DOCKER_RUN="docker run --rm --network host --add-host kafka:127.0.0.1 --env-file .env ${{ needs.container.outputs.tag }}" + + $DOCKER_RUN ./bin/migrate + $DOCKER_RUN python manage.py setup_dev + + # only starts the plugin server so that the "wait for PostHog" step passes + $DOCKER_RUN ./bin/docker-worker &> /tmp/logs/worker.txt & + $DOCKER_RUN ./bin/docker-server &> /tmp/logs/server.txt & + + - name: Wait for PostHog + # these are required checks so, we can't skip entire sections + if: needs.changes.outputs.shouldTriggerCypress == 'true' + # this action might be abandoned - but v1 doesn't point to latest of v1 (which it should) + # so pointing to v1.1.0 to remove warnings about node version with v1 + # todo check https://github.com/iFaxity/wait-on-action/releases for new releases + uses: iFaxity/wait-on-action@v1.1.0 + timeout-minutes: 3 + with: + verbose: true + log: true + resource: http://localhost:8000 + + - name: Cypress run + # these are required checks so, we can't skip entire sections + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: cypress-io/github-action@v6 + with: + config-file: cypress.e2e.config.ts + config: retries=2 + spec: ${{ matrix.chunk }} + install: false + env: + E2E_TESTING: 1 + OPT_OUT_CAPTURE: 0 + GITHUB_ACTION_RUN_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + + - name: Archive test screenshots + uses: actions/upload-artifact@v3 + with: + name: screenshots + path: cypress/screenshots + if: ${{ failure() }} + + - name: Archive test downloads + uses: actions/upload-artifact@v3 + with: + name: downloads + path: cypress/downloads + if: ${{ failure() }} + + - name: Archive test videos + uses: actions/upload-artifact@v3 + with: + name: videos + path: cypress/videos + if: ${{ failure() }} + + - name: Archive accessibility violations + if: needs.changes.outputs.shouldTriggerCypress == 'true' + uses: actions/upload-artifact@v3 + with: + name: accessibility-violations + path: '**/a11y/' + if-no-files-found: 'ignore' + + - name: Show logs on failure + # use artefact here, as I think the output will be too large for display in an action + uses: actions/upload-artifact@v3 + with: + name: logs-${{ strategy.job-index }} + path: /tmp/logs + if: ${{ failure() }} diff --git a/cypress/e2e/billingUpgradeCTA.cy.ts b/cypress/e2e/billingUpgradeCTA.cy.ts new file mode 100644 index 0000000000000..18504d2fe6a18 --- /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(3) + }) + + 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/billingv2.cy.ts b/cypress/e2e/billingv2.cy.ts index 13b819a441317..12db2073a1202 100644 --- a/cypress/e2e/billingv2.cy.ts +++ b/cypress/e2e/billingv2.cy.ts @@ -14,7 +14,7 @@ describe('Billing', () => { cy.get('[data-attr=more-button]').first().click() cy.contains('.LemonButton', 'Unsubscribe').click() - cy.get('.LemonModal h3').should('contain', 'Why are you unsubscribing from Product analytics + data stack?') + cy.get('.LemonModal h3').should('contain', 'Why are you unsubscribing from Product analytics?') cy.get('[data-attr=unsubscribe-reason-survey-textarea]').type('Product analytics') cy.contains('.LemonModal .LemonButton', 'Unsubscribe').click() @@ -35,7 +35,7 @@ describe('Billing', () => { it('Unsubscribe survey text area maintains unique state between product types', () => { cy.get('[data-attr=more-button]').first().click() cy.contains('.LemonButton', 'Unsubscribe').click() - cy.get('.LemonModal h3').should('contain', 'Why are you unsubscribing from Product analytics + data stack?') + cy.get('.LemonModal h3').should('contain', 'Why are you unsubscribing from Product analytics?') cy.get('[data-attr=unsubscribe-reason-survey-textarea]').type('Product analytics') cy.contains('.LemonModal .LemonButton', 'Cancel').click() diff --git a/cypress/e2e/onboarding.cy.ts b/cypress/e2e/onboarding.cy.ts index ecf7b86154b96..56cb23bc45b70 100644 --- a/cypress/e2e/onboarding.cy.ts +++ b/cypress/e2e/onboarding.cy.ts @@ -3,6 +3,8 @@ import { decideResponse } from '../fixtures/api/decide' describe('Onboarding', () => { beforeEach(() => { + cy.intercept('/api/billing-v2/', { fixture: 'api/billing-v2/billing-v2-unsubscribed.json' }) + cy.intercept('**/decide/*', (req) => req.reply( decideResponse({ @@ -21,24 +23,158 @@ describe('Onboarding', () => { // Confirm product intro is not included as the first step in the upper right breadcrumbs cy.get('[data-attr=onboarding-breadcrumbs] > :first-child > * span').should('not.contain', 'Product intro') - // Navigate to the product intro page by clicking the left side bar - cy.get('[data-attr=menu-item-replay').click() + cy.window().then((win) => { + win.POSTHOG_APP_CONTEXT.current_team.has_completed_onboarding_for = {} + }) + + cy.get('[data-attr=menu-item-savedinsights]').click() // Confirm we're on the product_intro page cy.get('[data-attr=top-bar-name] > span').contains('Onboarding') - cy.get('[data-attr=product-intro-title]').contains('Watch how users experience your app') + cy.get('[data-attr=product-intro-title]').contains('Product analytics with autocapture') - // Go back to /products - cy.visit('/products') + cy.get('[data-attr=start-onboarding]').should('be.visible') + cy.get('[data-attr=skip-onboarding]').should('not.exist') + }) - // Again get started on product analytics onboarding - cy.get('[data-attr=product_analytics-onboarding-card]').click() + // it('Step through PA onboarding', () => { + // cy.visit('/products') - // Navigate to the product intro page by changing the url - cy.visit(urls.onboarding('session_replay', 'product_intro')) + // // Get started on product analytics onboarding + // cy.get('[data-attr=product_analytics-onboarding-card]').click() - // Confirm we're on the product intro page - cy.get('[data-attr=top-bar-name] > span').contains('Onboarding') - cy.get('[data-attr=product-intro-title]').contains('Watch how users experience your app') - }) + // // Installation should be complete + // cy.get('svg.LemonIcon.text-success').should('exist') + // cy.get('svg.LemonIcon.text-success').parent().should('contain', 'Installation complete') + + // // Continue to configuration step + // cy.get('[data-attr=sdk-continue]').click() + + // // Confirm the appropriate breadcrumb is highlighted + // cy.get('[data-attr=onboarding-breadcrumbs] > :nth-child(3) > * span').should('contain', 'Configure') + // cy.get('[data-attr=onboarding-breadcrumbs] > :nth-child(3) > * span').should('not.have.css', 'text-muted') + + // // Continue to plans + // cy.get('[data-attr=onboarding-continue]').click() + + // // Verify pricing table visible + // cy.get('.BillingHero').should('be.visible') + // cy.get('table.PlanComparison').should('be.visible') + + // // Confirm buttons on pricing comparison + // cy.get('[data-attr=upgrade-Paid] .LemonButton__content').should('have.text', 'Upgrade') + // cy.get('[data-attr=upgrade-Free] .LemonButton__content').should('have.text', 'Current plan') + + // // Continue + // cy.get('[data-attr=onboarding-skip-button]').click() + + // // Click back to Install step + // cy.get('[data-attr=onboarding-breadcrumbs] > :first-child > * span').click() + + // // Continue through to finish + // cy.get('[data-attr=sdk-continue]').click() + // cy.get('[data-attr=onboarding-continue]').click() + // cy.get('[data-attr=onboarding-skip-button]').click() + // cy.get('[data-attr=onboarding-continue]').click() + + // // Confirm we're on the insights list page + // cy.url().should('contain', 'project/1/insights') + + // cy.visit('/onboarding/product_analytics?step=product_intro') + + // // Should see both an option to skip onboarding and an option to see the sdk instructions + // cy.get('[data-attr=skip-onboarding]').should('be.visible') + // cy.get('[data-attr=start-onboarding-sdk]').should('be.visible') + + // cy.get('[data-attr=skip-onboarding]').first().click() + // cy.url().should('contain', 'project/1/insights') + + // cy.visit('/onboarding/product_analytics?step=product_intro') + // cy.get('[data-attr=start-onboarding-sdk]').first().click() + // cy.url().should('contain', 'project/1/onboarding/product_analytics?step=install') + + // cy.visit('/products') + // cy.get('[data-attr=return-to-product_analytics] > svg').click() + // cy.url().should('contain', 'project/1/insights') + // }) + + // it('Step through SR onboarding', () => { + // cy.visit('/products') + // cy.get('[data-attr=session_replay-onboarding-card]').click() + + // // Installation should be complete + // cy.get('svg.LemonIcon.text-success').should('exist') + // cy.get('svg.LemonIcon.text-success').parent().should('contain', 'Installation complete') + // // Continue to configuration step + // cy.get('[data-attr=sdk-continue]').click() + // // Continue to plans + // cy.get('[data-attr=onboarding-continue]').click() + // // Verify pricing table visible + // cy.get('.BillingHero').should('be.visible') + // cy.get('table.PlanComparison').should('be.visible') + // // Confirm buttons on pricing comparison + // cy.get('[data-attr=upgrade-Paid] .LemonButton__content').should('have.text', 'Upgrade') + // cy.get('[data-attr=upgrade-Free] .LemonButton__content').should('have.text', 'Current plan') + // // Continue through to finish + // cy.get('[data-attr=onboarding-skip-button]').click() + // cy.get('[data-attr=onboarding-continue]').click() + // // Confirm we're on the recordings list page + // cy.url().should('contain', 'project/1/replay/recent') + // cy.visit('/onboarding/session_replay?step=product_intro') + // cy.get('[data-attr=skip-onboarding]').should('be.visible') + // cy.get('[data-attr=start-onboarding-sdk]').should('not.exist') + // }) + + // it('Step through FF onboarding', () => { + // cy.visit('/onboarding/feature_flags?step=product_intro') + // cy.get('[data-attr=start-onboarding-sdk]').first().click() + // cy.get('[data-attr=sdk-continue]').click() + + // // Confirm the appropriate breadcrumb is highlighted + // cy.get('[data-attr=onboarding-breadcrumbs] > :nth-child(5) > * span').should('contain', 'Plans') + // cy.get('[data-attr=onboarding-breadcrumbs] > :nth-child(3) > * span').should('not.have.css', 'text-muted') + + // cy.get('[data-attr=onboarding-skip-button]').click() + // cy.get('[data-attr=onboarding-continue]').click() + + // cy.url().should('contain', '/feature_flags') + + // cy.visit('/onboarding/feature_flags?step=product_intro') + + // cy.get('[data-attr=skip-onboarding]').should('be.visible') + // cy.get('[data-attr=start-onboarding-sdk]').should('be.visible') + + // cy.get('[data-attr=skip-onboarding]').first().click() + // }) + + // it('Step through Surveys onboarding', () => { + // cy.visit('/onboarding/surveys?step=product_intro') + // cy.get('[data-attr=skip-onboarding]').should('be.visible') + // cy.get('[data-attr=start-onboarding-sdk]').should('not.exist') + // cy.get('[data-attr=skip-onboarding]').first().click() + // cy.url().should('contain', 'survey_templates') + + // cy.visit('/products') + // cy.get('[data-attr=surveys-onboarding-card]').click() + // // Installation should be complete + // cy.get('svg.LemonIcon.text-success').should('exist') + // cy.get('svg.LemonIcon.text-success').parent().should('contain', 'Installation complete') + + // // Continue to configuration step + // cy.get('[data-attr=sdk-continue]').click() + + // // Verify pricing table visible + // cy.get('.BillingHero').should('be.visible') + // cy.get('table.PlanComparison').should('be.visible') + + // // Confirm buttons on pricing comparison + // cy.get('[data-attr=upgrade-Paid] .LemonButton__content').should('have.text', 'Upgrade') + // cy.get('[data-attr=upgrade-Free] .LemonButton__content').should('have.text', 'Current plan') + + // // Continue + // cy.get('[data-attr=onboarding-skip-button]').click() + // cy.get('[data-attr=onboarding-continue]').click() + + // cy.url().should('contain', '/survey_templates') + // }) }) diff --git a/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json b/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json index 60aed5c9693c4..32e1268032d34 100644 --- a/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json +++ b/cypress/fixtures/api/billing-v2/billing-v2-unsubscribed.json @@ -14,6 +14,7 @@ "discord_integration", "apps", "boolean_flags", + "multivariate_flags", "persist_flags_cross_authentication", "feature_flag_payloads", "multiple_release_conditions", @@ -21,6 +22,10 @@ "targeting_by_group", "local_evaluation_and_bootstrapping", "flag_usage_stats", + "experimentation", + "funnel_experiments", + "secondary_metrics", + "statistical_analysis", "feature_flags_data_retention", "console_logs", "recordings_performance", @@ -47,17 +52,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": [ @@ -173,6 +178,14 @@ "limit": null, "note": null }, + { + "key": "multivariate_flags", + "name": "Multivariate feature flags & experiments", + "description": "Create three or more variants of a feature flag to test or release different versions of a feature.", + "unit": null, + "limit": null, + "note": null + }, { "key": "persist_flags_cross_authentication", "name": "Persist flags across authentication", @@ -229,6 +242,38 @@ "limit": null, "note": null }, + { + "key": "experimentation", + "name": "A/B testing", + "description": "Test changes to your product and evaluate the impacts those changes make.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "funnel_experiments", + "name": "Funnel & trend experiments", + "description": "Measure the impact of a change on a aggregate values or a series of events, like a signup flow.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "secondary_metrics", + "name": "Secondary experiment metrics", + "description": "Track additional metrics to see how your experiment affects other parts of your app or different flows.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "statistical_analysis", + "name": "Statistical analysis", + "description": "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", + "unit": null, + "limit": null, + "note": null + }, { "key": "feature_flags_data_retention", "name": "Data retention", @@ -438,12 +483,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 +509,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 +560,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 +623,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 +631,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 +660,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 +669,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 +678,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 +687,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 +696,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 +705,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 +714,9 @@ } ], "current_plan": false, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], "type": "product_analytics", @@ -737,7 +754,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 +820,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 +849,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 +903,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 +930,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 +939,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 +948,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 +957,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 +966,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 +975,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 +996,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 +1025,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 +1034,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 +1043,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 +1052,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 +1061,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 +1070,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 +1079,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 +1152,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 +1171,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 +1245,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 +1368,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 +1561,9 @@ } ], "current_plan": false, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], "type": "session_replay", @@ -1753,7 +1746,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", @@ -1769,6 +1762,14 @@ "limit": null, "note": null }, + { + "key": "multivariate_flags", + "name": "Multivariate feature flags & experiments", + "description": "Create three or more variants of a feature flag to test or release different versions of a feature.", + "unit": null, + "limit": null, + "note": null + }, { "key": "persist_flags_cross_authentication", "name": "Persist flags across authentication", @@ -1825,6 +1826,38 @@ "limit": null, "note": null }, + { + "key": "experimentation", + "name": "A/B testing", + "description": "Test changes to your product and evaluate the impacts those changes make.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "funnel_experiments", + "name": "Funnel & trend experiments", + "description": "Measure the impact of a change on a aggregate values or a series of events, like a signup flow.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "secondary_metrics", + "name": "Secondary experiment metrics", + "description": "Track additional metrics to see how your experiment affects other parts of your app or different flows.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "statistical_analysis", + "name": "Statistical analysis", + "description": "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", + "unit": null, + "limit": null, + "note": null + }, { "key": "feature_flags_data_retention", "name": "Data retention", @@ -1836,12 +1869,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", @@ -1929,14 +1964,6 @@ "limit": null, "note": null }, - { - "key": "group_experiments", - "name": "Group experiments", - "description": "Target experiments to specific groups of users so everyone in the same group gets the same variant.", - "unit": null, - "limit": null, - "note": null - }, { "key": "funnel_experiments", "name": "Funnel & trend experiments", @@ -1961,6 +1988,22 @@ "limit": null, "note": null }, + { + "key": "group_experiments", + "name": "Group experiments", + "description": "Target experiments to specific groups of users so everyone in the same group gets the same variant.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "multiple_environments", + "name": "Multi-environment support", + "description": "Test flags in local development or staging by using the same flag key across PostHog projects.", + "unit": null, + "limit": null, + "note": null + }, { "key": "feature_flags_data_retention", "name": "Data retention", @@ -2018,7 +2061,9 @@ } ], "current_plan": false, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], "type": "feature_flags", @@ -2215,7 +2260,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 +2335,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 +2496,9 @@ } ], "current_plan": false, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], "type": "surveys", @@ -2617,7 +2666,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 +2717,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 +2783,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 +2871,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 +2928,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-20240208", + "product_key": "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": "paid-20230926", + "plan_key": "teams-20240208", "product_key": "platform_and_support", - "name": "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", @@ -2939,17 +3084,25 @@ "note": null }, { - "key": "project_based_permissioning", - "name": "Project permissions", - "description": "Restrict access to data within the organization to only those who need it.", + "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": "white_labelling", - "name": "White labeling", - "description": "Use your own branding in your PostHog organization.", + "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 @@ -2962,42 +3115,334 @@ "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": "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.", "unit": null, "limit": null, - "note": "$2k/month spend or above" + "note": "$2k+/month spend" }, { - "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": "priority_support", + "name": "Priority support", + "description": "Get help from our team faster than other customers.", "unit": null, "limit": null, "note": null }, { - "key": "terms_and_conditions", - "name": "Terms and conditions", - "description": "Terms and conditions", + "key": "white_labelling", + "name": "White labeling", + "description": "Use your own branding on surveys, shared dashboards, shared insights, and more.", "unit": null, "limit": null, - "note": "Standard" + "note": null }, { - "key": "security_assessment", - "name": "Security assessment", - "description": "Security assessment", + "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": "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 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 + }, + { + "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": null + }, + { + "key": "priority_support", + "name": "Priority support", + "description": "Get help from our team faster than other customers.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "security_assessment", + "name": "Security assessment", + "description": "Security assessment", + "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 +3460,7 @@ "projected_amount_usd": null, "unit": null, "addons": [], - "contact_support": true, + "contact_support": false, "inclusion_only": true, "features": [ { @@ -3051,17 +3496,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 +3519,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 +3543,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 +3577,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 +3609,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 +3654,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 +3730,11 @@ "discount_amount_usd": null, "amount_off_expires_at": null, "never_drop_data": null, - "stripe_portal_url": null + "customer_trust_scores": { + "surveys": 0, + "feature_flags": 0, + "session_replay": 3, + "product_analytics": 3 + }, + "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9QaEVaQ0hCTUE0aE8wUFhlVWVqd29MaElGd3lwRjFa010044U4IxJp" } diff --git a/cypress/fixtures/api/billing-v2/billing-v2.json b/cypress/fixtures/api/billing-v2/billing-v2.json index 605cbf2da1d86..4a8abf3ae41b5 100644 --- a/cypress/fixtures/api/billing-v2/billing-v2.json +++ b/cypress/fixtures/api/billing-v2/billing-v2.json @@ -7,6 +7,7 @@ "surveys_api_mode", "surveys_results_analysis", "surveys_templates", + "surveys_data_retention", "zapier", "slack_integration", "microsoft_teams_integration", @@ -14,6 +15,7 @@ "apps", "app_metrics", "boolean_flags", + "multivariate_flags", "persist_flags_cross_authentication", "feature_flag_payloads", "multiple_release_conditions", @@ -21,46 +23,53 @@ "targeting_by_group", "local_evaluation_and_bootstrapping", "flag_usage_stats", - "data_warehouse_manual_sync", - "data_warehouse_unified_querying", - "data_warehouse_insights_visualization", + "experimentation", + "funnel_experiments", + "secondary_metrics", + "statistical_analysis", + "feature_flags_data_retention", "console_logs", + "recordings_performance", + "session_replay_network_payloads", "recordings_playlists", + "session_replay_data_retention", + "replay_mask_sensitive_data", + "replay_sharing_embedding", + "replay_product_analytics_integration", + "replay_filter_person_properties", + "replay_filter_events", + "replay_dom_explorer", + "session_replay_sampling", + "replay_recording_duration_minimum", + "replay_feature_flag_based_recording", "dashboards", "funnels", "graphs_trends", "paths", "subscriptions", "paths_advanced", - "advanced_permissions", - "team_collaboration", - "ingestion_taxonomy", "correlation_analysis", - "tagging", "behavioral_cohort_filtering", + "product_analytics_data_retention", "tracked_users", - "data_retention", "team_members", "organizations_projects", "api_access", "social_sso", - "project_based_permissioning", - "white_labelling", "community_support", "dedicated_support", "email_support", - "terms_and_conditions", - "security_assessment" + "2fa" ], "license": { "plan": "dev" }, - "customer_id": "cus_Ot0pdGiqNz8M9J", + "customer_id": "cus_Pg7PIL8MsKi6bx", "deactivated": false, "has_active_subscription": true, "billing_period": { - "current_period_start": "2023-10-31T00:08:23Z", - "current_period_end": "2023-11-30T00:08:23Z", + "current_period_start": "2024-03-07T23:21:20Z", + "current_period_end": "2024-04-07T23:21:20Z", "interval": "month" }, "available_product_features": [ @@ -82,8 +91,8 @@ }, { "key": "surveys_user_targeting", - "name": "User property targeting", - "description": "Target users based on any of their user properties.", + "name": "Advanced user targeting", + "description": "Target by URL, user property, or feature flag when used with Feature flags.", "unit": null, "limit": null, "note": null @@ -99,15 +108,15 @@ { "key": "surveys_api_mode", "name": "API mode", - "description": "Create surveys via the API.", + "description": "Using PostHog.js? No more code required. But if want to create your own UI, we have a full API.", "unit": null, "limit": null, "note": null }, { "key": "surveys_results_analysis", - "name": "Results analysis", - "description": "Analyze your survey results including completion rates and drop offs.", + "name": "Aggregated results", + "description": "See feedback summarized and broken down per response, plus completion rates and drop offs.", "unit": null, "limit": null, "note": null @@ -120,6 +129,14 @@ "limit": null, "note": null }, + { + "key": "surveys_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "year", + "limit": 1, + "note": null + }, { "key": "zapier", "name": "Zapier", @@ -154,8 +171,8 @@ }, { "key": "apps", - "name": "CDP + Apps library", - "description": "Connect your data with 50+ apps including BigQuery, Redshift, and more.", + "name": "Apps", + "description": "Use apps to transform, filter, and modify your incoming data. (Export apps not included, see the Data pipelines addon for product analytics.)", "unit": null, "limit": null, "note": null @@ -176,6 +193,14 @@ "limit": null, "note": null }, + { + "key": "multivariate_flags", + "name": "Multivariate feature flags & experiments", + "description": "Create three or more variants of a feature flag to test or release different versions of a feature.", + "unit": null, + "limit": null, + "note": null + }, { "key": "persist_flags_cross_authentication", "name": "Persist flags across authentication", @@ -186,8 +211,8 @@ }, { "key": "feature_flag_payloads", - "name": "Payloads", - "description": "Send additional pieces of information (any valid JSON) to your app when a flag is matched for a user.", + "name": "Test changes without code", + "description": "Use JSON payloads to change text, visuals, or entire blocks of code without subsequent deployments.", "unit": null, "limit": null, "note": null @@ -195,7 +220,7 @@ { "key": "multiple_release_conditions", "name": "Multiple release conditions", - "description": "Target multiple groups of users with different release conditions for the same feature flag.", + "description": "Customize your rollout strategy by user or group properties, cohort, or trafic percentage.", "unit": null, "limit": null, "note": null @@ -233,33 +258,65 @@ "note": null }, { - "key": "data_warehouse_manual_sync", - "name": "Manual sync", - "description": "Sync your data to the warehouse using your cloud storage provider.", + "key": "experimentation", + "name": "A/B testing", + "description": "Test changes to your product and evaluate the impacts those changes make.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "funnel_experiments", + "name": "Funnel & trend experiments", + "description": "Measure the impact of a change on a aggregate values or a series of events, like a signup flow.", "unit": null, "limit": null, "note": null }, { - "key": "data_warehouse_unified_querying", - "name": "Unified querying", - "description": "Query all your business and product data directly inside PostHog.", + "key": "secondary_metrics", + "name": "Secondary experiment metrics", + "description": "Track additional metrics to see how your experiment affects other parts of your app or different flows.", "unit": null, "limit": null, "note": null }, { - "key": "data_warehouse_insights_visualization", - "name": "Insights", - "description": "Create insights from the data you import and add them to your PostHog dashboards.", + "key": "statistical_analysis", + "name": "Statistical analysis", + "description": "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", "unit": null, "limit": null, "note": null }, + { + "key": "feature_flags_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "year", + "limit": 1, + "note": null + }, { "key": "console_logs", "name": "Console logs", - "description": "Diagnose issues by inspecting errors in the user's network console", + "description": "Debug issues faster by browsing the user's console.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "recordings_performance", + "name": "Network performance on recordings", + "description": "See your end-user's network performance and information alongside session recordings.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "session_replay_network_payloads", + "name": "Network monitor", + "description": "Analyze performance and network calls.", "unit": null, "limit": null, "note": null @@ -273,116 +330,164 @@ "note": null }, { - "key": "dashboards", - "name": "Dashboards", - "description": "Save trends, funnels, and other insights for easy reference by your whole team.", + "key": "session_replay_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "month", + "limit": 1, + "note": null + }, + { + "key": "replay_mask_sensitive_data", + "name": "Block sensitive data", + "description": "Disable capturing data from any DOM element with HTML attributes or a customizable config.", "unit": null, "limit": null, "note": null }, { - "key": "funnels", - "name": "Funnels", - "description": "Visualize user dropoff between a sequence of events.", + "key": "replay_sharing_embedding", + "name": "Share and embed", + "description": "Share replays directly via URL or embed via iframe.", "unit": null, "limit": null, "note": null }, { - "key": "graphs_trends", - "name": "Graphs & trends", - "description": "Plot any number of events or actions over time.", + "key": "replay_product_analytics_integration", + "name": "Event timeline", + "description": "See a history of everything that happened in a user's session.", "unit": null, "limit": null, "note": null }, { - "key": "paths", - "name": "Paths", - "description": "Limited paths excludes: customizing path insights by setting the maximum number of paths, number of people on each path, how path names appear", + "key": "replay_filter_person_properties", + "name": "Filter person properties", + "description": "Filter by person properties to quickly find relevant recordings.", "unit": null, "limit": null, "note": null }, { - "key": "subscriptions", - "name": "Insight & dashboard subscriptions", - "description": "Create a subscription for any insight or dashboard in PostHog to receive regular reports with their updates.", + "key": "replay_filter_events", + "name": "Filter events", + "description": "Filter by events to quickly find relevant recordings.", "unit": null, "limit": null, "note": null }, { - "key": "paths_advanced", - "name": "Advanced paths", - "description": "Customize your path insights by setting the maximum number of paths, number of people on each path, and how path names should appear.", + "key": "replay_dom_explorer", + "name": "DOM Explorer", + "description": "Freeze snapshots of recordings and explore the DOM with your browser dev tools.", "unit": null, "limit": null, "note": null }, { - "key": "advanced_permissions", - "name": "Dashboard permissions", - "description": "Restrict access to dashboards within the organization to only those who need it.", + "key": "session_replay_sampling", + "name": "Sample recorded sessions", + "description": "Restrict the percentage of sessions that will be recorded.", "unit": null, "limit": null, "note": null }, { - "key": "team_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.", + "key": "replay_recording_duration_minimum", + "name": "Minimum duration", + "description": "Only record sessions longer than the minimum duration.", "unit": null, "limit": null, "note": null }, { - "key": "ingestion_taxonomy", - "name": "Ingestion taxonomy", - "description": "Ingestion taxonomy", + "key": "replay_feature_flag_based_recording", + "name": "Record via feature flag", + "description": "Only record sessions for users that have the flag enabled.", "unit": null, "limit": null, "note": null }, { - "key": "correlation_analysis", - "name": "Correlation analysis", - "description": "Automatically highlight significant factors that affect the conversion rate of users within a funnel.", + "key": "dashboards", + "name": "Dashboards", + "description": "Save trends, funnels, and other insights for easy reference by your whole team.", "unit": null, "limit": null, "note": null }, { - "key": "tagging", - "name": "Dashboard tags", - "description": "Organize dashboards with tags.", + "key": "funnels", + "name": "Funnels", + "description": "Visualize user dropoff between a sequence of events. See conversion rate over time, use flexible step ordering, set exclusion steps, and more.", "unit": null, "limit": null, "note": null }, { - "key": "behavioral_cohort_filtering", - "name": "Lifecycle cohorts", - "description": "Group users based on their long term behavior, such as whether they frequently performed an event, or have recently stopped performing an event.", + "key": "graphs_trends", + "name": "Graphs & trends", + "description": "Plot any number of events or actions over time.", "unit": null, "limit": null, "note": null }, { - "key": "tracked_users", - "name": "Tracked users", - "description": "Track users across devices and sessions.", + "key": "paths", + "name": "User paths", + "description": "Limited paths excludes: customizing path insights by setting the maximum number of paths, number of people on each path, how path names appear", "unit": null, "limit": null, - "note": "Unlimited" + "note": null + }, + { + "key": "subscriptions", + "name": "Insight & dashboard subscriptions", + "description": "Create a subscription for any insight or dashboard in PostHog to receive regular reports with their updates.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "paths_advanced", + "name": "Advanced paths", + "description": "Customize your path insights by setting the maximum number of paths, number of people on each path, and how path names should appear.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "correlation_analysis", + "name": "Correlation analysis", + "description": "Automatically highlight significant factors that affect the conversion rate of users within a funnel.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "behavioral_cohort_filtering", + "name": "Lifecycle", + "description": "Discover how your active users break down, highlighting those who have recently stopped being active or those who have just become active for the first time.", + "unit": null, + "limit": null, + "note": null }, { - "key": "data_retention", + "key": "product_analytics_data_retention", "name": "Data retention", "description": "Keep a historical record of your data.", + "unit": "years", + "limit": 7, + "note": null + }, + { + "key": "tracked_users", + "name": "Tracked users", + "description": "Track users across devices and sessions.", "unit": null, "limit": null, - "note": "7 years" + "note": "Unlimited" }, { "key": "team_members", @@ -396,9 +501,9 @@ "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" + "unit": "projects", + "limit": 2, + "note": null }, { "key": "api_access", @@ -416,22 +521,6 @@ "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": "white_labelling", - "name": "White labeling", - "description": "Use your own branding in your PostHog organization.", - "unit": null, - "limit": null, - "note": null - }, { "key": "community_support", "name": "Community support", @@ -442,32 +531,24 @@ }, { "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.", "unit": null, "limit": null, - "note": "$2k/month spend or above" + "note": "$2k+/month spend" }, { "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.", "unit": null, "limit": null, "note": null }, { - "key": "terms_and_conditions", - "name": "Terms and conditions", - "description": "Terms and conditions", - "unit": null, - "limit": null, - "note": "Standard" - }, - { - "key": "security_assessment", - "name": "Security assessment", - "description": "Security assessment", + "key": "2fa", + "name": "2FA", + "description": "Secure your PostHog account with two-factor authentication.", "unit": null, "limit": null, "note": null @@ -477,11 +558,13 @@ "current_total_amount_usd_after_discount": "0.00", "products": [ { - "name": "Product analytics + data stack", - "description": "Trends, funnels, path analysis, CDP + more.", + "name": "Product analytics", + "headline": "Product analytics with autocapture", + "description": "A comprehensive product analytics platform built to natively work with session replay, feature flags, A/B testing, and surveys.", "price_description": null, "usage_key": "events", - "image_url": "https://posthog.com/images/product/product-icons/product-analytics.svg", + "image_url": "https://posthog.com/images/products/product-analytics/product-analytics.png", + "screenshot_url": "https://posthog.com/images/products/product-analytics/screenshot-product-analytics.png", "icon_key": "IconGraph", "docs_url": "https://posthog.com/docs/product-analytics", "subscribed": true, @@ -489,9 +572,9 @@ { "plan_key": "free-20230117", "product_key": "product_analytics", - "name": "Product analytics + data stack", - "description": "Trends, funnels, path analysis, CDP + more.", - "image_url": "https://posthog.com/images/product/product-icons/product-analytics.svg", + "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", "note": null, "unit": "event", @@ -508,7 +591,7 @@ { "key": "funnels", "name": "Funnels", - "description": "Visualize user dropoff between a sequence of events.", + "description": "Visualize user dropoff between a sequence of events. See conversion rate over time, use flexible step ordering, set exclusion steps, and more.", "unit": null, "limit": null, "note": null @@ -523,23 +606,33 @@ }, { "key": "paths", - "name": "Paths", + "name": "User paths", "description": "Limited paths excludes: customizing path insights by setting the maximum number of paths, number of people on each path, how path names appear", "unit": null, "limit": null, "note": "Limited" + }, + { + "key": "product_analytics_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "year", + "limit": 1, + "note": null } ], "tiers": null, "current_plan": false, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null }, { - "plan_key": "paid-20230509", + "plan_key": "paid-20240111", "product_key": "product_analytics", - "name": "Product analytics + data stack", - "description": "Trends, funnels, path analysis, CDP + more.", - "image_url": "https://posthog.com/images/product/product-icons/product-analytics.svg", + "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", "note": null, "unit": "event", @@ -556,7 +649,7 @@ { "key": "funnels", "name": "Funnels", - "description": "Visualize user dropoff between a sequence of events.", + "description": "Visualize user dropoff between a sequence of events. See conversion rate over time, use flexible step ordering, set exclusion steps, and more.", "unit": null, "limit": null, "note": null @@ -571,7 +664,7 @@ }, { "key": "paths", - "name": "Paths", + "name": "User paths", "description": "Limited paths excludes: customizing path insights by setting the maximum number of paths, number of people on each path, how path names appear", "unit": null, "limit": null, @@ -593,30 +686,6 @@ "limit": null, "note": null }, - { - "key": "advanced_permissions", - "name": "Dashboard permissions", - "description": "Restrict access to dashboards within the organization to only those who need it.", - "unit": null, - "limit": null, - "note": null - }, - { - "key": "team_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", @@ -626,19 +695,19 @@ "note": null }, { - "key": "tagging", - "name": "Dashboard tags", - "description": "Organize dashboards with tags.", + "key": "behavioral_cohort_filtering", + "name": "Lifecycle", + "description": "Discover how your active users break down, highlighting those who have recently stopped being active or those who have just become active for the first time.", "unit": null, "limit": null, "note": null }, { - "key": "behavioral_cohort_filtering", - "name": "Lifecycle cohorts", - "description": "Group users based on their long term behavior, such as whether they frequently performed an event, or have recently stopped performing an event.", - "unit": null, - "limit": null, + "key": "product_analytics_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "years", + "limit": 7, "note": null } ], @@ -654,7 +723,7 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.0003068", + "unit_amount_usd": "0.00031", "up_to": 2000000, "current_amount_usd": "0.00", "current_usage": 0, @@ -708,7 +777,9 @@ } ], "current_plan": true, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], "type": "product_analytics", @@ -725,7 +796,7 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.0003068", + "unit_amount_usd": "0.00031", "up_to": 2000000, "current_amount_usd": "0.00", "current_usage": 0, @@ -782,7 +853,7 @@ "unit_amount_usd": null, "current_amount_usd_before_addons": "0.00", "current_amount_usd": "0.00", - "current_usage": 35, + "current_usage": 0, "usage_limit": null, "has_exceeded_limit": false, "percentage_usage": 0, @@ -876,7 +947,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", @@ -959,106 +1030,44 @@ } ], "current_plan": false, - "included_if": null - } - ], - "contact_support": false - } - ], - "contact_support": false, - "inclusion_only": false - }, - { - "name": "Session replay", - "description": "Searchable recordings of people using your app or website with console logs and behavioral bucketing.", - "price_description": null, - "usage_key": "recordings", - "image_url": "https://posthog.com/images/product/product-icons/session-replay.svg", - "icon_key": "IconRewindPlay", - "docs_url": "https://posthog.com/docs/session-replay", - "subscribed": true, - "plans": [ - { - "plan_key": "free-20230117", - "product_key": "session_replay", - "name": "Session replay", - "description": "Searchable recordings of people using your app or website with console logs and behavioral bucketing.", - "image_url": "https://posthog.com/images/product/product-icons/session-replay.svg", - "docs_url": "https://posthog.com/docs/session-replay", - "note": null, - "unit": "recording", - "free_allocation": 15000, - "features": [ - { - "key": "console_logs", - "name": "Console logs", - "description": "Diagnose issues by inspecting errors in the user's network console", - "unit": null, - "limit": null, - "note": null - }, - { - "key": "recordings_playlists", - "name": "Recording playlists", - "description": "Create playlists of certain session recordings to easily find and watch them again in the future.", - "unit": "playlists", - "limit": 5, - "note": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], - "tiers": null, - "current_plan": true, - "included_if": null + "contact_support": null }, { - "plan_key": "paid-20230117", - "product_key": "session_replay", - "name": "Session replay", - "description": "Searchable recordings of people using your app or website with console logs and behavioral bucketing.", - "image_url": "https://posthog.com/images/product/product-icons/session-replay.svg", - "docs_url": "https://posthog.com/docs/session-replay", - "note": null, - "unit": "recording", - "free_allocation": null, - "features": [ - { - "key": "console_logs", - "name": "Console logs", - "description": "Diagnose issues by inspecting errors in the user's network console", - "unit": null, - "limit": null, - "note": null - }, + "name": "Data pipelines", + "description": "Get your PostHog data into your data warehouse or other tools like BigQuery, Redshift, Customer.io, and more.", + "price_description": null, + "image_url": "None", + "icon_key": "IconDecisionTree", + "docs_url": "https://posthog.com/docs/cdp/batch-exports", + "type": "data_pipelines", + "tiers": [ { - "key": "recordings_playlists", - "name": "Recording playlists", - "description": "Create playlists of certain session recordings to easily find and watch them again in the future.", - "unit": null, - "limit": null, - "note": null + "flat_amount_usd": "0", + "unit_amount_usd": "0", + "up_to": 1000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null }, { - "key": "recordings_performance", - "name": "Network performance on recordings", - "description": "See your end-user's network performance and information alongside session recordings.", - "unit": null, - "limit": null, - "note": null + "flat_amount_usd": "0", + "unit_amount_usd": "0.000248", + "up_to": 2000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null }, - { - "key": "recordings_file_export", - "name": "Recordings file export", - "description": "Save session recordings as a file to your local filesystem.", - "unit": null, - "limit": null, - "note": null - } - ], - "tiers": [ { "flat_amount_usd": "0", - "unit_amount_usd": "0", - "up_to": 15000, + "unit_amount_usd": "0.000104", + "up_to": 15000000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1066,8 +1075,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.005", - "up_to": 50000, + "unit_amount_usd": "0.0000655", + "up_to": 50000000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1075,8 +1084,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.0045", - "up_to": 150000, + "unit_amount_usd": "0.0000364", + "up_to": 100000000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1084,8 +1093,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.004", - "up_to": 500000, + "unit_amount_usd": "0.0000187", + "up_to": 250000000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1093,7 +1102,7 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.0035", + "unit_amount_usd": "0.0000042", "up_to": null, "current_amount_usd": "0.00", "current_usage": 0, @@ -1101,109 +1110,384 @@ "projected_amount_usd": null } ], - "current_plan": false, - "included_if": null + "tiered": true, + "included_with_main_product": false, + "subscribed": false, + "unit": "event", + "unit_amount_usd": null, + "current_amount_usd": null, + "current_usage": 0, + "projected_usage": 0, + "projected_amount_usd": "0.00", + "plans": [ + { + "plan_key": "addon-20240111", + "product_key": "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", + "note": null, + "unit": "event", + "free_allocation": null, + "features": [ + { + "key": "data_pipelines", + "name": "Data pipelines", + "description": "Get your PostHog data into your data warehouse or other tools like BigQuery, Redshift, Customer.io, and more.", + "unit": null, + "limit": null, + "note": null + } + ], + "tiers": [ + { + "flat_amount_usd": "0", + "unit_amount_usd": "0", + "up_to": 1000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.000248", + "up_to": 2000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.000104", + "up_to": 15000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.0000655", + "up_to": 50000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.0000364", + "up_to": 100000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.0000187", + "up_to": 250000000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.0000042", + "up_to": null, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + } + ], + "current_plan": false, + "included_if": null, + "contact_support": null, + "unit_amount_usd": null + } + ], + "contact_support": null } ], - "type": "session_replay", - "free_allocation": 15000, - "tiers": null, - "tiered": true, - "unit_amount_usd": null, - "current_amount_usd_before_addons": null, - "current_amount_usd": null, - "current_usage": 0, - "usage_limit": 15000, - "has_exceeded_limit": false, - "percentage_usage": 0, - "projected_usage": 0, - "projected_amount_usd": null, - "unit": "recording", - "addons": [], "contact_support": false, - "inclusion_only": false + "inclusion_only": false, + "features": [ + { + "key": "product_analytics_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "dashboards", + "name": "Dashboards", + "description": "Save trends, funnels, and other insights for easy reference by your whole team.", + "images": { + "light": "https://posthog.com/images/products/product-analytics/screenshot-dashboards.png", + "dark": "https://posthog.com/images/products/product-analytics/screenshot-dashboards-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "funnels", + "name": "Funnels", + "description": "Visualize user dropoff between a sequence of events. See conversion rate over time, use flexible step ordering, set exclusion steps, and more.", + "images": { + "light": "https://posthog.com/images/products/product-analytics/screenshot-funnels.png", + "dark": "https://posthog.com/images/products/product-analytics/screenshot-funnels-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "graphs_trends", + "name": "Graphs & trends", + "description": "Plot any number of events or actions over time.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "paths", + "name": "User paths", + "description": "Limited paths excludes: customizing path insights by setting the maximum number of paths, number of people on each path, how path names appear", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "insights", + "name": "Unlimited Insights", + "description": "Trends, funnels, retention, user paths, stickiness, and lifecycle insights to visualize your data.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "subscriptions", + "name": "Insight & dashboard subscriptions", + "description": "Create a subscription for any insight or dashboard in PostHog to receive regular reports with their updates.", + "images": null, + "icon_key": "IconNotification", + "type": "secondary" + }, + { + "key": "paths_advanced", + "name": "Advanced paths", + "description": "Customize your path insights by setting the maximum number of paths, number of people on each path, and how path names should appear.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "correlation_analysis", + "name": "Correlation analysis", + "description": "Automatically highlight significant factors that affect the conversion rate of users within a funnel.", + "images": { + "light": "https://posthog.com/images/products/product-analytics/screenshot-correlation-analysis.png", + "dark": "https://posthog.com/images/products/product-analytics/screenshot-correlation-analysis-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "behavioral_cohort_filtering", + "name": "Lifecycle", + "description": "Discover how your active users break down, highlighting those who have recently stopped being active or those who have just become active for the first time.", + "images": { + "light": "https://posthog.com/images/products/product-analytics/screenshot-lifecycle.png", + "dark": "https://posthog.com/images/products/product-analytics/screenshot-lifecycle-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "product_analytics_retention", + "name": "Retention", + "description": "See how many users return on subsequent days after performing an event the first time, or recurrently.", + "images": { + "light": "https://posthog.com/images/products/product-analytics/screenshot-retention.png", + "dark": "https://posthog.com/images/products/product-analytics/screenshot-retention-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "product_analytics_stickiness", + "name": "Stickiness", + "description": "Learn how many times users perform a specific event in a period of time.", + "images": { + "light": "https://posthog.com/images/products/product-analytics/screenshot-stickiness.png", + "dark": "https://posthog.com/images/products/product-analytics/screenshot-stickiness-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "autocapture", + "name": "Autocapture", + "description": "Add PostHog.js to your website or web app to track all event data and retroactively define events.", + "images": null, + "icon_key": "IconBolt", + "type": "secondary" + }, + { + "key": "data_visualization", + "name": "Data visualization", + "description": "Filter data by user property, group data, and use formulas in queries.", + "images": null, + "icon_key": "IconPieChart", + "type": "secondary" + }, + { + "key": "product_analytics_sql_queries", + "name": "Query with SQL", + "description": "Use PostHog’s filtering interface or switch into SQL mode for more powerful querying.", + "images": null, + "icon_key": "IconTerminal", + "type": "secondary" + } + ] }, { - "name": "Feature flags & A/B testing", - "description": "Safely roll out new features and run experiments on changes.", + "name": "Session replay", + "headline": "Watch how users experience your app", + "description": "Session replay helps you diagnose issues and understand user behavior in your product or website.", "price_description": null, - "usage_key": "feature_flag_requests", - "image_url": "https://posthog.com/images/product/product-icons/feature-flags.svg", - "icon_key": "IconToggle", - "docs_url": "https://posthog.com/docs/feature-flags", - "subscribed": false, + "usage_key": "recordings", + "image_url": "https://posthog.com/images/products/session-replay/session-replay.png", + "screenshot_url": "https://posthog.com/images/products/session-replay/screenshot-session-replay.png", + "icon_key": "IconRewindPlay", + "docs_url": "https://posthog.com/docs/session-replay", + "subscribed": true, "plans": [ { - "plan_key": "free-20230117", - "product_key": "feature_flags", - "name": "Feature flags & A/B testing", - "description": "Safely roll out new features and run experiments on changes.", - "image_url": "https://posthog.com/images/product/product-icons/feature-flags.svg", - "docs_url": "https://posthog.com/docs/feature-flags", + "plan_key": "free-20231218", + "product_key": "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", "note": null, - "unit": "request", - "free_allocation": 1000000, + "unit": "recording", + "free_allocation": 5000, "features": [ { - "key": "boolean_flags", - "name": "Boolean feature flags", - "description": "Turn features on and off for specific users.", + "key": "console_logs", + "name": "Console logs", + "description": "Debug issues faster by browsing the user's console.", "unit": null, "limit": null, "note": null }, { - "key": "persist_flags_cross_authentication", - "name": "Persist flags across authentication", - "description": "Persist feature flags across authentication events so that flag values don't change when an anonymous user logs in and becomes identified.", + "key": "recordings_performance", + "name": "Network performance on recordings", + "description": "See your end-user's network performance and information alongside session recordings.", "unit": null, "limit": null, "note": null }, { - "key": "feature_flag_payloads", - "name": "Payloads", - "description": "Send additional pieces of information (any valid JSON) to your app when a flag is matched for a user.", + "key": "session_replay_network_payloads", + "name": "Network monitor", + "description": "Analyze performance and network calls.", "unit": null, "limit": null, "note": null }, { - "key": "multiple_release_conditions", - "name": "Multiple release conditions", - "description": "Target multiple groups of users with different release conditions for the same feature flag.", + "key": "recordings_playlists", + "name": "Recording playlists", + "description": "Create playlists of certain session recordings to easily find and watch them again in the future.", + "unit": "playlists", + "limit": 5, + "note": null + }, + { + "key": "session_replay_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "month", + "limit": 1, + "note": null + }, + { + "key": "replay_mask_sensitive_data", + "name": "Block sensitive data", + "description": "Disable capturing data from any DOM element with HTML attributes or a customizable config.", "unit": null, "limit": null, "note": null }, { - "key": "release_condition_overrides", - "name": "Release condition overrides", - "description": "For any release condition, specify which flag value the users or groups in that condition should receive.", + "key": "replay_sharing_embedding", + "name": "Share and embed", + "description": "Share replays directly via URL or embed via iframe.", "unit": null, "limit": null, "note": null }, { - "key": "targeting_by_group", - "name": "Flag targeting by groups", - "description": "Target feature flag release conditions by group properties, not just user properties.", + "key": "replay_product_analytics_integration", + "name": "Event timeline", + "description": "See a history of everything that happened in a user's session.", "unit": null, "limit": null, "note": null }, { - "key": "local_evaluation_and_bootstrapping", - "name": "Local evaluation & bootstrapping", - "description": "Bootstrap flags on initialization so all flags are available immediately, without having to make extra network requests.", + "key": "replay_filter_person_properties", + "name": "Filter person properties", + "description": "Filter by person properties to quickly find relevant recordings.", "unit": null, "limit": null, "note": null }, { - "key": "flag_usage_stats", - "name": "Flag usage stats", - "description": "See how many times a flag has been evaluated, how many times each variant has been returned, and what values users received.", + "key": "replay_filter_events", + "name": "Filter events", + "description": "Filter by events to quickly find relevant recordings.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "replay_dom_explorer", + "name": "DOM Explorer", + "description": "Freeze snapshots of recordings and explore the DOM with your browser dev tools.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "session_replay_sampling", + "name": "Sample recorded sessions", + "description": "Restrict the percentage of sessions that will be recorded.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "replay_recording_duration_minimum", + "name": "Minimum duration", + "description": "Only record sessions longer than the minimum duration.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "replay_feature_flag_based_recording", + "name": "Record via feature flag", + "description": "Only record sessions for users that have the flag enabled.", "unit": null, "limit": null, "note": null @@ -1211,127 +1495,137 @@ ], "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", - "description": "Safely roll out new features and run experiments on changes.", - "image_url": "https://posthog.com/images/product/product-icons/feature-flags.svg", - "docs_url": "https://posthog.com/docs/feature-flags", + "plan_key": "paid-20231218", + "product_key": "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", "note": null, - "unit": "request", + "unit": "recording", "free_allocation": null, "features": [ { - "key": "boolean_flags", - "name": "Boolean feature flags", - "description": "Turn features on and off for specific users.", + "key": "console_logs", + "name": "Console logs", + "description": "Debug issues faster by browsing the user's console.", "unit": null, "limit": null, "note": null }, { - "key": "multivariate_flags", - "name": "Multivariate feature flags & experiments", - "description": "Create three or more variants of a feature flag to test or release different versions of a feature.", + "key": "recordings_playlists", + "name": "Recording playlists", + "description": "Create playlists of certain session recordings to easily find and watch them again in the future.", "unit": null, "limit": null, "note": null }, { - "key": "persist_flags_cross_authentication", - "name": "Persist flags across authentication", - "description": "Persist feature flags across authentication events so that flag values don't change when an anonymous user logs in and becomes identified.", + "key": "recordings_performance", + "name": "Network performance on recordings", + "description": "See your end-user's network performance and information alongside session recordings.", "unit": null, "limit": null, "note": null }, { - "key": "feature_flag_payloads", - "name": "Payloads", - "description": "Send additional pieces of information (any valid JSON) to your app when a flag is matched for a user.", + "key": "session_replay_network_payloads", + "name": "Network monitor", + "description": "Analyze performance and network calls.", "unit": null, "limit": null, "note": null }, { - "key": "multiple_release_conditions", - "name": "Multiple release conditions", - "description": "Target multiple groups of users with different release conditions for the same feature flag.", + "key": "recordings_file_export", + "name": "Download recordings", + "description": "Retain recordings beyond data retention limits.", "unit": null, "limit": null, "note": null }, { - "key": "release_condition_overrides", - "name": "Release condition overrides", - "description": "For any release condition, specify which flag value the users or groups in that condition should receive.", + "key": "session_replay_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "months", + "limit": 3, + "note": null + }, + { + "key": "replay_mask_sensitive_data", + "name": "Block sensitive data", + "description": "Disable capturing data from any DOM element with HTML attributes or a customizable config.", "unit": null, "limit": null, "note": null }, { - "key": "targeting_by_group", - "name": "Flag targeting by groups", - "description": "Target feature flag release conditions by group properties, not just user properties.", + "key": "replay_sharing_embedding", + "name": "Share and embed", + "description": "Share replays directly via URL or embed via iframe.", "unit": null, "limit": null, "note": null }, { - "key": "local_evaluation_and_bootstrapping", - "name": "Local evaluation & bootstrapping", - "description": "Bootstrap flags on initialization so all flags are available immediately, without having to make extra network requests.", + "key": "replay_product_analytics_integration", + "name": "Event timeline", + "description": "See a history of everything that happened in a user's session.", "unit": null, "limit": null, "note": null }, { - "key": "flag_usage_stats", - "name": "Flag usage stats", - "description": "See how many times a flag has been evaluated, how many times each variant has been returned, and what values users received.", + "key": "replay_filter_person_properties", + "name": "Filter person properties", + "description": "Filter by person properties to quickly find relevant recordings.", "unit": null, "limit": null, "note": null }, { - "key": "experimentation", - "name": "A/B testing", - "description": "Test changes to your product and evaluate the impacts those changes make.", + "key": "replay_filter_events", + "name": "Filter events", + "description": "Filter by events to quickly find relevant recordings.", "unit": null, "limit": null, "note": null }, { - "key": "group_experiments", - "name": "Group experiments", - "description": "Target experiments to specific groups of users so everyone in the same group gets the same variant.", + "key": "replay_dom_explorer", + "name": "DOM Explorer", + "description": "Freeze snapshots of recordings and explore the DOM with your browser dev tools.", "unit": null, "limit": null, "note": null }, { - "key": "funnel_experiments", - "name": "Funnel & trend experiments", - "description": "Measure the impact of a change on a aggregate values or a series of events, like a signup flow.", + "key": "session_replay_sampling", + "name": "Sample recorded sessions", + "description": "Restrict the percentage of sessions that will be recorded.", "unit": null, "limit": null, "note": null }, { - "key": "secondary_metrics", - "name": "Secondary experiment metrics", - "description": "Track additional metrics to see how your experiment affects other parts of your app or different flows.", + "key": "replay_recording_duration_minimum", + "name": "Minimum duration", + "description": "Only record sessions longer than the minimum duration.", "unit": null, "limit": null, "note": null }, { - "key": "statistical_analysis", - "name": "Statistical analysis", - "description": "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", + "key": "replay_feature_flag_based_recording", + "name": "Record via feature flag", + "description": "Only record sessions for users that have the flag enabled.", "unit": null, "limit": null, "note": null @@ -1341,7 +1635,7 @@ { "flat_amount_usd": "0", "unit_amount_usd": "0", - "up_to": 1000000, + "up_to": 5000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1349,8 +1643,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.0001", - "up_to": 2000000, + "unit_amount_usd": "0.04", + "up_to": 15000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1358,8 +1652,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.000045", - "up_to": 10000000, + "unit_amount_usd": "0.003", + "up_to": 50000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1367,8 +1661,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.000025", - "up_to": 50000000, + "unit_amount_usd": "0.0027", + "up_to": 150000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1376,7 +1670,16 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.00001", + "unit_amount_usd": "0.0025", + "up_to": 500000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.002", "up_to": null, "current_amount_usd": "0.00", "current_usage": 0, @@ -1385,224 +1688,472 @@ } ], "current_plan": false, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], - "type": "feature_flags", - "free_allocation": 1000000, + "type": "session_replay", + "free_allocation": 5000, "tiers": null, "tiered": true, "unit_amount_usd": null, "current_amount_usd_before_addons": null, "current_amount_usd": null, "current_usage": 0, - "usage_limit": 1000000, + "usage_limit": 5000, "has_exceeded_limit": false, - "percentage_usage": 0, + "percentage_usage": 0.0, "projected_usage": 0, "projected_amount_usd": null, - "unit": "request", + "unit": "recording", "addons": [], "contact_support": false, - "inclusion_only": false + "inclusion_only": false, + "features": [ + { + "key": "recordings_playlists", + "name": "Recording playlists", + "description": "Create playlists of certain session recordings to easily find and watch them again in the future.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "session_replay_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "console_logs", + "name": "Console logs", + "description": "Debug issues faster by browsing the user's console.", + "images": { + "light": "https://posthog.com/images/products/session-replay/console.png", + "dark": "https://posthog.com/images/products/session-replay/console-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "recordings_performance", + "name": "Network performance on recordings", + "description": "See your end-user's network performance and information alongside session recordings.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "session_replay_network_payloads", + "name": "Network monitor", + "description": "Analyze performance and network calls.", + "images": { + "light": "https://posthog.com/images/products/session-replay/network.png", + "dark": "https://posthog.com/images/products/session-replay/network-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "recordings_file_export", + "name": "Download recordings", + "description": "Retain recordings beyond data retention limits.", + "images": null, + "icon_key": "IconDownload", + "type": "secondary" + }, + { + "key": "session_replay_sampling", + "name": "Sample recorded sessions", + "description": "Restrict the percentage of sessions that will be recorded.", + "images": null, + "icon_key": "IconSampling", + "type": "secondary" + }, + { + "key": "replay_recording_duration_minimum", + "name": "Minimum duration", + "description": "Only record sessions longer than the minimum duration.", + "images": null, + "icon_key": "IconClock", + "type": "secondary" + }, + { + "key": "replay_feature_flag_based_recording", + "name": "Record via feature flag", + "description": "Only record sessions for users that have the flag enabled.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "replay_mask_sensitive_data", + "name": "Block sensitive data", + "description": "Disable capturing data from any DOM element with HTML attributes or a customizable config.", + "images": null, + "icon_key": "IconPassword", + "type": "secondary" + }, + { + "key": "replay_sharing_embedding", + "name": "Share and embed", + "description": "Share replays directly via URL or embed via iframe.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "replay_product_analytics_integration", + "name": "Event timeline", + "description": "See a history of everything that happened in a user's session.", + "images": { + "light": "https://posthog.com/images/products/session-replay/timeline.png", + "dark": "https://posthog.com/images/products/session-replay/timeline-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "replay_filter_person_properties", + "name": "Filter person properties", + "description": "Filter by person properties to quickly find relevant recordings.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "replay_filter_events", + "name": "Filter events", + "description": "Filter by events to quickly find relevant recordings.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "replay_dom_explorer", + "name": "DOM Explorer", + "description": "Freeze snapshots of recordings and explore the DOM with your browser dev tools.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "works_with_posthog_js", + "name": "Capture sessions without extra code", + "description": "Works with PostHog.js", + "images": null, + "icon_key": "IconBolt", + "type": "secondary" + }, + { + "key": "replay_automatic_playlists", + "name": "Automatic playlists", + "description": "Filter by user behavior, user properties, or time.", + "images": null, + "icon_key": "IconPlaylist", + "type": "secondary" + } + ] }, { - "name": "Surveys", - "description": "Collect feedback from your users. Multiple choice, rating, open text, and more.", + "name": "Feature flags & A/B testing", + "headline": "Safely roll out features and A/B tests to specific users or groups", + "description": "Test changes with small groups of users before rolling out wider. Analyze usage with product analytics and session replay.", "price_description": null, - "usage_key": "survey_responses", - "image_url": "https://posthog.com/images/product/product-icons/surveys.svg", - "icon_key": "IconMessage", - "docs_url": "https://posthog.com/docs/surveys", + "usage_key": "feature_flag_requests", + "image_url": "https://posthog.com/images/products/feature-flags/feature-flags.png", + "screenshot_url": "https://posthog.com/images/products/feature-flags/screenshot-feature-flags.png", + "icon_key": "IconToggle", + "docs_url": "https://posthog.com/docs/feature-flags", "subscribed": false, "plans": [ { - "plan_key": "free-20230928", - "product_key": "surveys", - "name": "Surveys", - "description": "Collect feedback from your users. Multiple choice, rating, open text, and more.", - "image_url": "https://posthog.com/images/product/product-icons/surveys.svg", - "docs_url": "https://posthog.com/docs/surveys", + "plan_key": "free-20230117", + "product_key": "feature_flags", + "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", "note": null, - "unit": "survey response", - "free_allocation": 250, + "unit": "request", + "free_allocation": 1000000, "features": [ { - "key": "surveys_unlimited_surveys", - "name": "Unlimited surveys", - "description": "Create as many surveys as you want.", + "key": "boolean_flags", + "name": "Boolean feature flags", + "description": "Turn features on and off for specific users.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_all_question_types", - "name": "All question types", - "description": "Rating scale (for NPS and the like), multiple choice, single choice, emoji rating, link, free text.", + "key": "multivariate_flags", + "name": "Multivariate feature flags & experiments", + "description": "Create three or more variants of a feature flag to test or release different versions of a feature.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_user_targeting", - "name": "User property targeting", - "description": "Target users based on any of their user properties.", + "key": "persist_flags_cross_authentication", + "name": "Persist flags across authentication", + "description": "Persist feature flags across authentication events so that flag values don't change when an anonymous user logs in and becomes identified.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_user_sampling", - "name": "User sampling", - "description": "Sample users to only survey a portion of the users who match the criteria.", + "key": "feature_flag_payloads", + "name": "Test changes without code", + "description": "Use JSON payloads to change text, visuals, or entire blocks of code without subsequent deployments.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_api_mode", - "name": "API mode", - "description": "Create surveys via the API.", + "key": "multiple_release_conditions", + "name": "Multiple release conditions", + "description": "Customize your rollout strategy by user or group properties, cohort, or trafic percentage.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_results_analysis", - "name": "Results analysis", - "description": "Analyze your survey results including completion rates and drop offs.", + "key": "release_condition_overrides", + "name": "Release condition overrides", + "description": "For any release condition, specify which flag value the users or groups in that condition should receive.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_templates", - "name": "Templates", - "description": "Use our templates to get started quickly with NPS, customer satisfaction surveys, user interviews, and more.", + "key": "targeting_by_group", + "name": "Flag targeting by groups", + "description": "Target feature flag release conditions by group properties, not just user properties.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "local_evaluation_and_bootstrapping", + "name": "Local evaluation & bootstrapping", + "description": "Bootstrap flags on initialization so all flags are available immediately, without having to make extra network requests.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "flag_usage_stats", + "name": "Flag usage stats", + "description": "See how many times a flag has been evaluated, how many times each variant has been returned, and what values users received.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "experimentation", + "name": "A/B testing", + "description": "Test changes to your product and evaluate the impacts those changes make.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "funnel_experiments", + "name": "Funnel & trend experiments", + "description": "Measure the impact of a change on a aggregate values or a series of events, like a signup flow.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "secondary_metrics", + "name": "Secondary experiment metrics", + "description": "Track additional metrics to see how your experiment affects other parts of your app or different flows.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "statistical_analysis", + "name": "Statistical analysis", + "description": "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", "unit": null, "limit": null, "note": null + }, + { + "key": "feature_flags_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "year", + "limit": 1, + "note": null } ], "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", - "description": "Collect feedback from your users. Multiple choice, rating, open text, and more.", - "image_url": "https://posthog.com/images/product/product-icons/surveys.svg", - "docs_url": "https://posthog.com/docs/surveys", + "plan_key": "paid-20230623", + "product_key": "feature_flags", + "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", "note": null, - "unit": "survey response", + "unit": "request", "free_allocation": null, "features": [ { - "key": "surveys_unlimited_surveys", - "name": "Unlimited surveys", - "description": "Create as many surveys as you want.", + "key": "boolean_flags", + "name": "Boolean feature flags", + "description": "Turn features on and off for specific users.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_all_question_types", - "name": "All question types", - "description": "Rating scale (for NPS and the like), multiple choice, single choice, emoji rating, link, free text.", + "key": "multivariate_flags", + "name": "Multivariate feature flags & experiments", + "description": "Create three or more variants of a feature flag to test or release different versions of a feature.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_multiple_questions", - "name": "Multiple questions", - "description": "Create multiple questions in a single survey.", + "key": "persist_flags_cross_authentication", + "name": "Persist flags across authentication", + "description": "Persist feature flags across authentication events so that flag values don't change when an anonymous user logs in and becomes identified.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_user_targeting", - "name": "User property targeting", - "description": "Target users based on any of their user properties.", + "key": "feature_flag_payloads", + "name": "Test changes without code", + "description": "Use JSON payloads to change text, visuals, or entire blocks of code without subsequent deployments.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_user_sampling", - "name": "User sampling", - "description": "Sample users to only survey a portion of the users who match the criteria.", + "key": "multiple_release_conditions", + "name": "Multiple release conditions", + "description": "Customize your rollout strategy by user or group properties, cohort, or trafic percentage.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_styling", - "name": "Custom colors & positioning", - "description": "Customize the colors of your surveys to match your brand and set survey position.", + "key": "release_condition_overrides", + "name": "Release condition overrides", + "description": "For any release condition, specify which flag value the users or groups in that condition should receive.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_text_html", - "name": "Custom HTML text", - "description": "Add custom HTML to your survey text.", + "key": "targeting_by_group", + "name": "Flag targeting by groups", + "description": "Target feature flag release conditions by group properties, not just user properties.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_api_mode", - "name": "API mode", - "description": "Create surveys via the API.", + "key": "local_evaluation_and_bootstrapping", + "name": "Local evaluation & bootstrapping", + "description": "Bootstrap flags on initialization so all flags are available immediately, without having to make extra network requests.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_results_analysis", - "name": "Results analysis", - "description": "Analyze your survey results including completion rates and drop offs.", + "key": "flag_usage_stats", + "name": "Flag usage stats", + "description": "See how many times a flag has been evaluated, how many times each variant has been returned, and what values users received.", "unit": null, "limit": null, "note": null }, { - "key": "surveys_templates", - "name": "Templates", - "description": "Use our templates to get started quickly with NPS, customer satisfaction surveys, user interviews, and more.", + "key": "experimentation", + "name": "A/B testing", + "description": "Test changes to your product and evaluate the impacts those changes make.", "unit": null, "limit": null, "note": null - } - ], - "tiers": [ + }, { - "flat_amount_usd": "0", - "unit_amount_usd": "0", - "up_to": 250, - "current_amount_usd": "0.00", - "current_usage": 0, - "projected_usage": null, - "projected_amount_usd": null + "key": "funnel_experiments", + "name": "Funnel & trend experiments", + "description": "Measure the impact of a change on a aggregate values or a series of events, like a signup flow.", + "unit": null, + "limit": null, + "note": null }, { - "flat_amount_usd": "0", - "unit_amount_usd": "0.2", - "up_to": 500, - "current_amount_usd": "0.00", + "key": "secondary_metrics", + "name": "Secondary experiment metrics", + "description": "Track additional metrics to see how your experiment affects other parts of your app or different flows.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "statistical_analysis", + "name": "Statistical analysis", + "description": "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "group_experiments", + "name": "Group experiments", + "description": "Target experiments to specific groups of users so everyone in the same group gets the same variant.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "multiple_environments", + "name": "Multi-environment support", + "description": "Test flags in local development or staging by using the same flag key across PostHog projects.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "feature_flags_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "years", + "limit": 7, + "note": null + } + ], + "tiers": [ + { + "flat_amount_usd": "0", + "unit_amount_usd": "0", + "up_to": 1000000, + "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, "projected_amount_usd": null }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.1", - "up_to": 1000, + "unit_amount_usd": "0.0001", + "up_to": 2000000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1610,8 +2161,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.035", - "up_to": 10000, + "unit_amount_usd": "0.000045", + "up_to": 10000000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1619,8 +2170,8 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.015", - "up_to": 20000, + "unit_amount_usd": "0.000025", + "up_to": 50000000, "current_amount_usd": "0.00", "current_usage": 0, "projected_usage": null, @@ -1628,7 +2179,7 @@ }, { "flat_amount_usd": "0", - "unit_amount_usd": "0.01", + "unit_amount_usd": "0.00001", "up_to": null, "current_amount_usd": "0.00", "current_usage": 0, @@ -1637,224 +2188,612 @@ } ], "current_plan": false, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null } ], - "type": "surveys", - "free_allocation": 250, + "type": "feature_flags", + "free_allocation": 1000000, "tiers": null, "tiered": true, "unit_amount_usd": null, "current_amount_usd_before_addons": null, "current_amount_usd": null, "current_usage": 0, - "usage_limit": 250, + "usage_limit": 1000000, "has_exceeded_limit": false, - "percentage_usage": 0, + "percentage_usage": 0.0, "projected_usage": 0, "projected_amount_usd": null, - "unit": "survey response", + "unit": "request", "addons": [], "contact_support": false, - "inclusion_only": false + "inclusion_only": false, + "features": [ + { + "key": "boolean_flags", + "name": "Boolean feature flags", + "description": "Turn features on and off for specific users.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "feature_flags_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "multivariate_flags", + "name": "Multivariate feature flags & experiments", + "description": "Create three or more variants of a feature flag to test or release different versions of a feature.", + "images": { + "light": "https://posthog.com/images/products/feature-flags/multivariate.png", + "dark": "https://posthog.com/images/products/feature-flags/multivariate-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "persist_flags_cross_authentication", + "name": "Persist flags across authentication", + "description": "Persist feature flags across authentication events so that flag values don't change when an anonymous user logs in and becomes identified.", + "images": null, + "icon_key": "IconUnlock", + "type": "secondary" + }, + { + "key": "feature_flag_payloads", + "name": "Test changes without code", + "description": "Use JSON payloads to change text, visuals, or entire blocks of code without subsequent deployments.", + "images": { + "light": "https://posthog.com/images/products/feature-flags/payloads.png", + "dark": "https://posthog.com/images/products/feature-flags/payloads-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "multiple_release_conditions", + "name": "Multiple release conditions", + "description": "Customize your rollout strategy by user or group properties, cohort, or trafic percentage.", + "images": { + "light": "https://posthog.com/images/products/feature-flags/release-conditions.png", + "dark": "https://posthog.com/images/products/feature-flags/release-conditions-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "release_condition_overrides", + "name": "Release condition overrides", + "description": "For any release condition, specify which flag value the users or groups in that condition should receive.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "targeting_by_group", + "name": "Flag targeting by groups", + "description": "Target feature flag release conditions by group properties, not just user properties.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "local_evaluation_and_bootstrapping", + "name": "Local evaluation & bootstrapping", + "description": "Bootstrap flags on initialization so all flags are available immediately, without having to make extra network requests.", + "images": null, + "icon_key": "IconDecisionTree", + "type": "secondary" + }, + { + "key": "flag_usage_stats", + "name": "Flag usage stats", + "description": "See how many times a flag has been evaluated, how many times each variant has been returned, and what values users received.", + "images": { + "light": "https://posthog.com/images/products/feature-flags/reports.png", + "dark": "https://posthog.com/images/products/feature-flags/reports-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "multiple_environments", + "name": "Multi-environment support", + "description": "Test flags in local development or staging by using the same flag key across PostHog projects.", + "images": null, + "icon_key": "IconStack", + "type": "secondary" + }, + { + "key": "user_opt_in", + "name": "Early access feature opt-in widget", + "description": "Allow users to opt in to (or out of) specified features. Or use the API to build your own UI.", + "images": { + "light": "https://posthog.com/images/products/feature-flags/early-access.png", + "dark": "https://posthog.com/images/products/feature-flags/early-access-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "instant_rollbacks", + "name": "Instant rollbacks", + "description": "Disable a feature without touching your codebase.", + "images": null, + "icon_key": "IconRevert", + "type": "secondary" + }, + { + "key": "experimentation", + "name": "A/B testing", + "description": "Test changes to your product and evaluate the impacts those changes make.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "group_experiments", + "name": "Group experiments", + "description": "Target experiments to specific groups of users so everyone in the same group gets the same variant.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "funnel_experiments", + "name": "Funnel & trend experiments", + "description": "Measure the impact of a change on a aggregate values or a series of events, like a signup flow.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "secondary_metrics", + "name": "Secondary experiment metrics", + "description": "Track additional metrics to see how your experiment affects other parts of your app or different flows.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "statistical_analysis", + "name": "Statistical analysis", + "description": "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", + "images": null, + "icon_key": null, + "type": null + } + ] }, { - "name": "Data warehouse", - "description": "A single source for all your important data. This product is in beta. Pricing will be changing.", + "name": "Surveys", + "headline": "Ask anything with no-code surveys", + "description": "Build in-app popups with freeform text responses, multiple choice, NPS, ratings, and emoji reactions. Or use the API for complete control.", "price_description": null, - "usage_key": "synced_rows", - "image_url": "https://posthog.com/images/product/product-icons/data-warehouse.svg", - "icon_key": "IconServer", - "docs_url": "https://posthog.com/docs/data-warehouse", + "usage_key": "survey_responses", + "image_url": "https://posthog.com/images/products/surveys/surveys.png", + "screenshot_url": "https://posthog.com/images/products/surveys/screenshot-surveys.png", + "icon_key": "IconMessage", + "docs_url": "https://posthog.com/docs/surveys", "subscribed": false, "plans": [ { - "plan_key": "free-20231026", - "product_key": "data_warehouse", - "name": "Data warehouse", - "description": "A single source for all your important data. This product is in beta. Pricing will be changing.", - "image_url": "https://posthog.com/images/product/product-icons/data-warehouse.svg", - "docs_url": "https://posthog.com/docs/data-warehouse", + "plan_key": "free-20230928", + "product_key": "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", "note": null, - "unit": "row", - "free_allocation": null, + "unit": "survey response", + "free_allocation": 250, "features": [ { - "key": "data_warehouse_manual_sync", - "name": "Manual sync", - "description": "Sync your data to the warehouse using your cloud storage provider.", + "key": "surveys_unlimited_surveys", + "name": "Unlimited surveys", + "description": "Create as many surveys as you want.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "surveys_all_question_types", + "name": "All question types", + "description": "Rating scale (for NPS and the like), multiple choice, single choice, emoji rating, link, free text.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "surveys_user_targeting", + "name": "Advanced user targeting", + "description": "Target by URL, user property, or feature flag when used with Feature flags.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "surveys_user_sampling", + "name": "User sampling", + "description": "Sample users to only survey a portion of the users who match the criteria.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "surveys_api_mode", + "name": "API mode", + "description": "Using PostHog.js? No more code required. But if want to create your own UI, we have a full API.", "unit": null, "limit": null, "note": null }, { - "key": "data_warehouse_unified_querying", - "name": "Unified querying", - "description": "Query all your business and product data directly inside PostHog.", + "key": "surveys_results_analysis", + "name": "Aggregated results", + "description": "See feedback summarized and broken down per response, plus completion rates and drop offs.", "unit": null, "limit": null, "note": null }, { - "key": "data_warehouse_insights_visualization", - "name": "Insights", - "description": "Create insights from the data you import and add them to your PostHog dashboards.", + "key": "surveys_templates", + "name": "Templates", + "description": "Use our templates to get started quickly with NPS, customer satisfaction surveys, user interviews, and more.", "unit": null, "limit": null, "note": null + }, + { + "key": "surveys_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "year", + "limit": 1, + "note": null } ], "tiers": null, "current_plan": true, - "included_if": null + "included_if": null, + "contact_support": null, + "unit_amount_usd": null }, { - "plan_key": "paid-20231026", - "product_key": "data_warehouse", - "name": "Data warehouse", - "description": "A single source for all your important data. This product is in beta. Pricing will be changing.", - "image_url": "https://posthog.com/images/product/product-icons/data-warehouse.svg", - "docs_url": "https://posthog.com/docs/data-warehouse", + "plan_key": "paid-20230928", + "product_key": "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", "note": null, - "unit": "row", + "unit": "survey response", "free_allocation": null, "features": [ { - "key": "data_warehouse_manual_sync", - "name": "Manual sync", - "description": "Sync your data to the warehouse using your cloud storage provider.", + "key": "surveys_unlimited_surveys", + "name": "Unlimited surveys", + "description": "Create as many surveys as you want.", "unit": null, "limit": null, "note": null }, { - "key": "data_warehouse_one_click_sync", - "name": "One-Click sync", - "description": "Sync your data to the warehouse with one click.", + "key": "surveys_all_question_types", + "name": "All question types", + "description": "Rating scale (for NPS and the like), multiple choice, single choice, emoji rating, link, free text.", "unit": null, "limit": null, "note": null }, { - "key": "data_warehouse_unified_querying", - "name": "Unified querying", - "description": "Query all your business and product data directly inside PostHog.", + "key": "surveys_multiple_questions", + "name": "Multiple questions", + "description": "Ask up to 10 questions in a single survey.", "unit": null, "limit": null, "note": null }, { - "key": "data_warehouse_insights_visualization", - "name": "Insights", - "description": "Create insights from the data you import and add them to your PostHog dashboards.", + "key": "surveys_user_targeting", + "name": "Advanced user targeting", + "description": "Target by URL, user property, or feature flag when used with Feature flags.", "unit": null, "limit": null, "note": null - } - ], - "tiers": [ + }, { - "flat_amount_usd": "0", - "unit_amount_usd": "0.000015", - "up_to": null, - "current_amount_usd": "0.00", - "current_usage": 0, - "projected_usage": null, - "projected_amount_usd": null - } - ], - "current_plan": false, - "included_if": null - } - ], - "type": "data_warehouse", - "free_allocation": 0, - "tiers": null, - "tiered": false, - "unit_amount_usd": null, - "current_amount_usd_before_addons": null, - "current_amount_usd": null, - "current_usage": 0, - "usage_limit": 0, - "has_exceeded_limit": false, - "percentage_usage": 0, - "projected_usage": 0, - "projected_amount_usd": null, - "unit": "row", - "addons": [], - "contact_support": false, - "inclusion_only": false - }, - { - "name": "Integrations + CDP", - "description": "Connect PostHog to your favorite tools.", - "price_description": null, - "usage_key": null, - "image_url": "https://posthog.com/images/product/product-icons/integrations.svg", - "icon_key": "IconBolt", - "docs_url": "https://posthog.com/docs/apps", - "subscribed": null, - "plans": [ - { - "plan_key": "free-20230117", - "product_key": "integrations", - "name": "Integrations + CDP", - "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", - "note": null, - "unit": null, - "free_allocation": null, - "features": [ + "key": "surveys_user_sampling", + "name": "User sampling", + "description": "Sample users to only survey a portion of the users who match the criteria.", + "unit": null, + "limit": null, + "note": null + }, { - "key": "zapier", - "name": "Zapier", - "description": "Zapier lets you connect PostHog with thousands of the most popular apps, so you can automate your work and have more time for what matters most—no code required.", + "key": "surveys_styling", + "name": "Custom colors & positioning", + "description": "Customize the colors of your surveys to match your brand and set survey position.", "unit": null, "limit": null, "note": null }, { - "key": "slack_integration", - "name": "Slack", - "description": "Get notified about new actions in Slack.", + "key": "surveys_text_html", + "name": "Custom HTML text", + "description": "Add custom HTML to your survey text.", "unit": null, "limit": null, "note": null }, { - "key": "microsoft_teams_integration", - "name": "Microsoft Teams", - "description": "Get notified about new actions in Microsoft Teams.", + "key": "surveys_api_mode", + "name": "API mode", + "description": "Using PostHog.js? No more code required. But if want to create your own UI, we have a full API.", "unit": null, "limit": null, "note": null }, { - "key": "discord_integration", - "name": "Discord", - "description": "Get notified about new actions in Discord.", + "key": "surveys_results_analysis", + "name": "Aggregated results", + "description": "See feedback summarized and broken down per response, plus completion rates and drop offs.", "unit": null, "limit": null, "note": null }, { - "key": "apps", - "name": "CDP + Apps library", - "description": "Connect your data with 50+ apps including BigQuery, Redshift, and more.", + "key": "surveys_templates", + "name": "Templates", + "description": "Use our templates to get started quickly with NPS, customer satisfaction surveys, user interviews, and more.", "unit": null, "limit": null, "note": null + }, + { + "key": "surveys_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "unit": "years", + "limit": 7, + "note": null } ], - "tiers": null, - "current_plan": false, - "included_if": "no_active_subscription" - }, - { - "plan_key": "paid-20230117", + "tiers": [ + { + "flat_amount_usd": "0", + "unit_amount_usd": "0", + "up_to": 250, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.2", + "up_to": 500, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.1", + "up_to": 1000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.035", + "up_to": 10000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.015", + "up_to": 20000, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + }, + { + "flat_amount_usd": "0", + "unit_amount_usd": "0.01", + "up_to": null, + "current_amount_usd": "0.00", + "current_usage": 0, + "projected_usage": null, + "projected_amount_usd": null + } + ], + "current_plan": false, + "included_if": null, + "contact_support": null, + "unit_amount_usd": null + } + ], + "type": "surveys", + "free_allocation": 250, + "tiers": null, + "tiered": true, + "unit_amount_usd": null, + "current_amount_usd_before_addons": null, + "current_amount_usd": null, + "current_usage": 0, + "usage_limit": 250, + "has_exceeded_limit": false, + "percentage_usage": 0.0, + "projected_usage": 0, + "projected_amount_usd": null, + "unit": "survey response", + "addons": [], + "contact_support": false, + "inclusion_only": false, + "features": [ + { + "key": "surveys_unlimited_surveys", + "name": "Unlimited surveys", + "description": "Create as many surveys as you want.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "surveys_all_question_types", + "name": "All question types", + "description": "Rating scale (for NPS and the like), multiple choice, single choice, emoji rating, link, free text.", + "images": { + "light": "https://posthog.com/images/products/surveys/question-types.png", + "dark": "https://posthog.com/images/products/surveys/question-types-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "surveys_multiple_questions", + "name": "Multiple questions", + "description": "Ask up to 10 questions in a single survey.", + "images": { + "light": "https://posthog.com/images/products/surveys/steps.png", + "dark": "https://posthog.com/images/products/surveys/steps-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "surveys_user_targeting", + "name": "Advanced user targeting", + "description": "Target by URL, user property, or feature flag when used with Feature flags.", + "images": { + "light": "https://posthog.com/images/products/surveys/targeting.png", + "dark": "https://posthog.com/images/products/surveys/targeting-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "surveys_user_sampling", + "name": "User sampling", + "description": "Sample users to only survey a portion of the users who match the criteria.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "surveys_styling", + "name": "Custom colors & positioning", + "description": "Customize the colors of your surveys to match your brand and set survey position.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "surveys_text_html", + "name": "Custom HTML text", + "description": "Add custom HTML to your survey text.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "surveys_api_mode", + "name": "API mode", + "description": "Using PostHog.js? No more code required. But if want to create your own UI, we have a full API.", + "images": { + "light": "https://posthog.com/images/products/surveys/api.png", + "dark": "https://posthog.com/images/products/surveys/api-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "surveys_results_analysis", + "name": "Aggregated results", + "description": "See feedback summarized and broken down per response, plus completion rates and drop offs.", + "images": null, + "icon_key": "IconPieChart", + "type": "secondary" + }, + { + "key": "surveys_templates", + "name": "Templates", + "description": "Use our templates to get started quickly with NPS, customer satisfaction surveys, user interviews, and more.", + "images": { + "light": "https://posthog.com/images/products/surveys/templates.png", + "dark": "https://posthog.com/images/products/surveys/templates-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "surveys_data_retention", + "name": "Data retention", + "description": "Keep a historical record of your data.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "surveys_link_question_type", + "name": "Link somewhere", + "description": "Send users to a webpage or invite them to book a meeting with a calendar invite.", + "images": { + "light": "https://posthog.com/images/products/surveys/link-scheduler.png", + "dark": "https://posthog.com/images/products/surveys/link-scheduler-dark.png" + }, + "icon_key": null, + "type": "primary" + }, + { + "key": "surveys_slack_notifications", + "name": "Slack notifications", + "description": "Send realtime survey responses to a Slack channel.", + "images": null, + "icon_key": "IconNotification", + "type": "secondary" + }, + { + "key": "surveys_wait_periods", + "name": "Customizable wait periods", + "description": "Set a delay before a survey opens.", + "images": null, + "icon_key": "IconClock", + "type": "secondary" + } + ] + }, + { + "name": "Integrations", + "headline": null, + "description": "Connect PostHog to your favorite tools.", + "price_description": null, + "usage_key": null, + "image_url": "https://posthog.com/images/product/product-icons/integrations.svg", + "screenshot_url": null, + "icon_key": "IconBolt", + "docs_url": "https://posthog.com/docs/apps", + "subscribed": null, + "plans": [ + { + "plan_key": "free-20230117", "product_key": "integrations", - "name": "Integrations + CDP", + "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", @@ -1871,83 +2810,545 @@ "note": null }, { - "key": "slack_integration", - "name": "Slack", - "description": "Get notified about new actions in Slack.", + "key": "slack_integration", + "name": "Slack", + "description": "Get notified about new actions in Slack.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "microsoft_teams_integration", + "name": "Microsoft Teams", + "description": "Get notified about new actions in Microsoft Teams.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "discord_integration", + "name": "Discord", + "description": "Get notified about new actions in Discord.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "apps", + "name": "Apps", + "description": "Use apps to transform, filter, and modify your incoming data. (Export apps not included, see the Data pipelines addon for product analytics.)", + "unit": null, + "limit": null, + "note": null + } + ], + "tiers": null, + "current_plan": false, + "included_if": "no_active_subscription", + "contact_support": null, + "unit_amount_usd": null + }, + { + "plan_key": "paid-20230117", + "product_key": "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", + "note": null, + "unit": null, + "free_allocation": null, + "features": [ + { + "key": "zapier", + "name": "Zapier", + "description": "Zapier lets you connect PostHog with thousands of the most popular apps, so you can automate your work and have more time for what matters most—no code required.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "slack_integration", + "name": "Slack", + "description": "Get notified about new actions in Slack.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "microsoft_teams_integration", + "name": "Microsoft Teams", + "description": "Get notified about new actions in Microsoft Teams.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "discord_integration", + "name": "Discord", + "description": "Get notified about new actions in Discord.", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "apps", + "name": "Apps", + "description": "Use apps to transform, filter, and modify your incoming data. (Export apps not included, see the Data pipelines addon for product analytics.)", + "unit": null, + "limit": null, + "note": null + }, + { + "key": "app_metrics", + "name": "App metrics", + "description": "Get metrics on your apps to see their usage, reliability, and more.", + "unit": null, + "limit": null, + "note": null + } + ], + "tiers": null, + "current_plan": true, + "included_if": "has_subscription", + "contact_support": null, + "unit_amount_usd": null + } + ], + "type": "integrations", + "free_allocation": 0, + "tiers": null, + "tiered": false, + "unit_amount_usd": null, + "current_amount_usd_before_addons": null, + "current_amount_usd": null, + "current_usage": 0, + "usage_limit": 0, + "has_exceeded_limit": false, + "percentage_usage": 0, + "projected_usage": 0, + "projected_amount_usd": null, + "unit": null, + "addons": [], + "contact_support": false, + "inclusion_only": true, + "features": [ + { + "key": "apps", + "name": "Apps", + "description": "Use apps to transform, filter, and modify your incoming data. (Export apps not included, see the Data pipelines addon for product analytics.)", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "slack_integration", + "name": "Slack", + "description": "Get notified about new actions in Slack.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "microsoft_teams_integration", + "name": "Microsoft Teams", + "description": "Get notified about new actions in Microsoft Teams.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "discord_integration", + "name": "Discord", + "description": "Get notified about new actions in Discord.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "zapier", + "name": "Zapier", + "description": "Zapier lets you connect PostHog with thousands of the most popular apps, so you can automate your work and have more time for what matters most—no code required.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "app_metrics", + "name": "App metrics", + "description": "Get metrics on your apps to see their usage, reliability, and more.", + "images": null, + "icon_key": null, + "type": null + } + ] + }, + { + "name": "Platform and support", + "headline": null, + "description": "SSO, permission management, and support.", + "price_description": null, + "usage_key": null, + "image_url": "https://posthog.com/images/product/product-icons/platform.svg", + "screenshot_url": null, + "icon_key": "IconStack", + "docs_url": "https://posthog.com/docs", + "subscribed": null, + "plans": [ + { + "plan_key": "free-20230117", + "product_key": "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", + "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": "project", + "limit": 1, + "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": "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": "no_active_subscription", + "contact_support": null, + "unit_amount_usd": null + }, + { + "plan_key": "paid-20240208", + "product_key": "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": true, + "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", + "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": 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": "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": "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", + "description": "Restrict access to data within the organization to only those who need it.", + "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": "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": "microsoft_teams_integration", - "name": "Microsoft Teams", - "description": "Get notified about new actions in Microsoft Teams.", + "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": "discord_integration", - "name": "Discord", - "description": "Get notified about new actions in Discord.", + "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": "apps", - "name": "CDP + Apps library", - "description": "Connect your data with 50+ apps including BigQuery, Redshift, and more.", + "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": "app_metrics", - "name": "App metrics", - "description": "Get metrics on your apps to see their usage, reliability, and more.", + "key": "tagging", + "name": "Dashboard tags", + "description": "Organize dashboards with tags.", "unit": null, "limit": null, "note": null } ], - "tiers": null, - "current_plan": true, - "included_if": "has_subscription" - } - ], - "type": "integrations", - "free_allocation": 0, - "tiers": null, - "tiered": false, - "unit_amount_usd": null, - "current_amount_usd_before_addons": null, - "current_amount_usd": null, - "current_usage": 0, - "usage_limit": 0, - "has_exceeded_limit": false, - "percentage_usage": 0, - "projected_usage": 0, - "projected_amount_usd": null, - "unit": null, - "addons": [], - "contact_support": false, - "inclusion_only": true - }, - { - "name": "Platform and support", - "description": "SSO, permission management, and support.", - "price_description": null, - "usage_key": null, - "image_url": "https://posthog.com/images/product/product-icons/platform.svg", - "icon_key": "IconStack", - "docs_url": "https://posthog.com/docs", - "subscribed": null, - "plans": [ + "tiers": [], + "current_plan": false, + "included_if": null, + "contact_support": null, + "unit_amount_usd": "450.00" + }, { - "plan_key": "free-20230117", + "plan_key": "enterprise-20240208", "product_key": "platform_and_support", - "name": "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", @@ -1955,6 +3356,22 @@ "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", @@ -1964,33 +3381,41 @@ "note": "Unlimited" }, { - "key": "data_retention", - "name": "Data retention", - "description": "Keep a historical record of your data.", - "unit": "year", - "limit": 1, + "key": "api_access", + "name": "API access", + "description": "Access your data via our developer-friendly API.", + "unit": null, + "limit": null, "note": null }, { - "key": "team_members", - "name": "Team members", - "description": "PostHog doesn't charge per seat add your entire team!", + "key": "white_labelling", + "name": "White labeling", + "description": "Use your own branding on surveys, shared dashboards, shared insights, and more.", "unit": null, "limit": null, - "note": "Unlimited" + "note": null }, { - "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": "project", - "limit": 1, + "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": "api_access", - "name": "API access", - "description": "Access your data via our developer-friendly API.", + "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 @@ -2004,97 +3429,81 @@ "note": null }, { - "key": "community_support", - "name": "Community support", - "description": "Get help from other users and PostHog team members in our Community forums.", + "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": "terms_and_conditions", - "name": "Terms and conditions", - "description": "Terms and conditions", + "key": "saml", + "name": "SAML SSO", + "description": "Allow your organization's users to log in with SAML.", "unit": null, "limit": null, - "note": "Standard" - } - ], - "tiers": null, - "current_plan": false, - "included_if": "no_active_subscription" - }, - { - "plan_key": "paid-20230926", - "product_key": "platform_and_support", - "name": "Platform and support", - "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": [ + "note": null + }, { - "key": "tracked_users", - "name": "Tracked users", - "description": "Track users across devices and sessions.", + "key": "2fa", + "name": "2FA", + "description": "Secure your PostHog account with two-factor authentication.", "unit": null, "limit": null, - "note": "Unlimited" + "note": null }, { - "key": "data_retention", - "name": "Data retention", - "description": "Keep a historical record of your data.", + "key": "2fa_enforcement", + "name": "Enforce 2FA", + "description": "Require all users in your organization to enable two-factor authentication.", "unit": null, "limit": null, - "note": "7 years" + "note": null }, { - "key": "team_members", - "name": "Team members", - "description": "PostHog doesn't charge per seat add your entire team!", + "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": "Unlimited" + "note": null }, { - "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.", + "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": "Unlimited" + "note": null }, { - "key": "api_access", - "name": "API access", - "description": "Access your data via our developer-friendly API.", + "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": "social_sso", - "name": "SSO via Google, Github, or Gitlab", - "description": "Log in to PostHog with your Google, Github, or Gitlab account.", + "key": "audit_logs", + "name": "Audit logs", + "description": "See who in your organization has accessed or modified entities within PostHog.", "unit": null, "limit": null, - "note": null + "note": "Advanced" }, { - "key": "project_based_permissioning", - "name": "Project permissions", - "description": "Restrict access to data within the organization to only those who need it.", + "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": "white_labelling", - "name": "White labeling", - "description": "Use your own branding in your PostHog organization.", + "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 @@ -2108,28 +3517,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", @@ -2138,11 +3547,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": true, - "included_if": "has_subscription" + "current_plan": false, + "included_if": null, + "contact_support": true, + "unit_amount_usd": null } ], "type": "platform_and_support", @@ -2160,8 +3587,250 @@ "projected_amount_usd": null, "unit": null, "addons": [], - "contact_support": true, - "inclusion_only": true + "contact_support": false, + "inclusion_only": true, + "features": [ + { + "key": "tracked_users", + "name": "Tracked users", + "description": "Track users across devices and sessions.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "team_members", + "name": "Team members", + "description": "PostHog doesn't charge per seat add your entire team!", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "api_access", + "name": "API access", + "description": "Access your data via our developer-friendly API.", + "images": null, + "icon_key": null, + "type": null + }, + { + "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.", + "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.", + "images": null, + "icon_key": null, + "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.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "project_based_permissioning", + "name": "Project permissions", + "description": "Restrict access to data within the organization to only those who need it.", + "images": null, + "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", + "description": "Allow your organization's users to log in with SAML.", + "images": null, + "icon_key": null, + "type": 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.", + "images": null, + "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 on surveys, shared dashboards, shared insights, and more.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "community_support", + "name": "Community support", + "description": "Get help from other users and PostHog team members in our Community forums.", + "images": null, + "icon_key": null, + "type": 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.", + "images": null, + "icon_key": null, + "type": 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.", + "images": null, + "icon_key": null, + "type": null + }, + { + "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": "Ongoing training", + "description": "Get training from our team to help you quickly get up and running with PostHog.", + "images": null, + "icon_key": null, + "type": 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.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "terms_and_conditions", + "name": "Terms and conditions", + "description": "Terms and conditions", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "security_assessment", + "name": "Security assessment", + "description": "Security assessment", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "bespoke_pricing", + "name": "Bespoke pricing", + "description": "Custom pricing to fit your company's needs.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "invoice_payments", + "name": "Payment via invoicing", + "description": "Pay for your PostHog subscription via invoice.", + "images": null, + "icon_key": null, + "type": null + }, + { + "key": "support_slas", + "name": "Support SLAs", + "description": "Support SLAs", + "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 + } + ] } ], "custom_limits_usd": {}, @@ -2172,7 +3841,7 @@ }, "recordings": { "usage": 0, - "limit": 15000 + "limit": 5000 }, "feature_flag_requests": { "usage": 0, @@ -2181,10 +3850,6 @@ "survey_responses": { "usage": 0, "limit": 250 - }, - "synced_rows": { - "usage": 0, - "limit": 0 } }, "free_trial_until": null, @@ -2192,5 +3857,11 @@ "discount_amount_usd": null, "amount_off_expires_at": null, "never_drop_data": null, - "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9PdXdxeDNqcktEWWdEM1FhalNRNmNCdTZCaUJsVVBi01006f6sniQg" + "customer_trust_scores": { + "surveys": 0, + "feature_flags": 0, + "session_replay": 3, + "product_analytics": 3 + }, + "stripe_portal_url": "https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9QaEVpVHFJNXdKYk9DaG04SVhMaUV4TDlxOTR1WEZi0100SMJCDr2e" } 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/__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-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-insights-error-empty-states--long-loading--dark.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--dark.png index e0f2ce83d97d5..7c9894110c558 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--dark.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--light.png b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--light.png index f0eb7967c5b1e..8e15b103248ed 100644 Binary files a/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--light.png and b/frontend/__snapshots__/scenes-app-insights-error-empty-states--long-loading--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-other-billing-v2--billing-v-2--dark.png b/frontend/__snapshots__/scenes-other-billing-v2--billing-v-2--dark.png index f3521aa3d9ba9..b760d16fd5592 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..f23ba81faf042 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-organization--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-organization--dark.png index ae4c62ad0824c..ab33e8563f5f7 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-organization--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-organization--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-organization--light.png b/frontend/__snapshots__/scenes-other-settings--settings-organization--light.png index b0aae8a4b0b27..08a1cffcd03ae 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-organization--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-organization--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 ( + + ) +} + +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 ( +
    +

    {title}

    + {children} +
    + ) +} + +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 ( <> - +
    - +
    + +
    + +
    +
      + {PRODUCTS.map((product, index) => ( +
    • + +
      + {product.icon} + + {product.name} + +
      +
      + +
      + +
    • + ))} +
    +
    -
    +
    +

    + Questions about features, how to's, or use cases? There are thousands of discussions in our + community forums. +

    - Submit - - - Cancel + Ask a question -
    + + +
    +
      +
    • + } + 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! +

    + +
      +
    1. + Search our docs +

      + We're constantly updating our docs and tutorials to provide the latest + information about installing, using, and troubleshooting. +

      +
    2. +
    3. + 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. + +

      +
    4. +
    5. + + Explore PostHog partners + +

      + Third-party providers can help with installation and debugging of data issues. +

      +
    6. +
    7. + Upgrade to a paid plan +

      + Our paid plans offer email support.{' '} + + Explore options. + +

      +
    8. +
    +
    + )}
    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.tsx b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx index 8f6cb1e96a68b..673bd426629bf 100644 --- a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx @@ -129,7 +129,7 @@ export function PropertyValue({ value={formattedValues} mode={isMultiSelect ? 'multiple' : 'single'} allowCustomValues - onChange={(nextVal) => setValue(nextVal)} + onChange={(nextVal) => (isMultiSelect ? setValue(nextVal) : setValue(nextVal[0]))} onInputChange={onSearchTextChange} placeholder={placeholder} options={displayOptions.map(({ name: _name }, index) => { 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, - }))} - /> - )} + + <> +
    + + + 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/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/LemonInputSelect/LemonInputSelect.tsx b/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.tsx index f3e39c46f1e11..967f18e323753 100644 --- a/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.tsx +++ b/frontend/src/lib/lemon-ui/LemonInputSelect/LemonInputSelect.tsx @@ -157,7 +157,7 @@ export function LemonInputSelect({ } } else if (e.key === 'ArrowDown') { e.preventDefault() - setSelectedIndex(Math.min(selectedIndex + 1, options.length - 1)) + setSelectedIndex(Math.min(selectedIndex + 1, visibleOptions.length - 1)) } else if (e.key === 'ArrowUp') { e.preventDefault() setSelectedIndex(Math.max(selectedIndex - 1, 0)) 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/utils/eventUsageLogic.ts b/frontend/src/lib/utils/eventUsageLogic.ts index 555f99cb715a1..e88617ddd7bad 100644 --- a/frontend/src/lib/utils/eventUsageLogic.ts +++ b/frontend/src/lib/utils/eventUsageLogic.ts @@ -507,8 +507,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/mocks/fixtures/_billing_v2.tsx b/frontend/src/mocks/fixtures/_billing_v2.tsx index 017c2ed16bc6b..43b499844eb70 100644 --- a/frontend/src/mocks/fixtures/_billing_v2.tsx +++ b/frontend/src/mocks/fixtures/_billing_v2.tsx @@ -3,12 +3,12 @@ import { dayjs } from 'lib/dayjs' import { BillingV2Type } from '~/types' export const billingJson: BillingV2Type = { - customer_id: 'cus_PRQtW3VM1Kiw7e', + customer_id: 'cus_Pg7PIL8MsKi6bx', deactivated: false, has_active_subscription: true, billing_period: { - current_period_start: dayjs('2023-05-01T23:59:59Z'), - current_period_end: dayjs('2023-06-01T23:59:59Z'), + current_period_start: dayjs('2024-03-07T22:54:32Z'), + current_period_end: dayjs('2024-04-07T22:54:32Z'), interval: 'month', }, current_total_amount_usd: '403.07', @@ -16,12 +16,13 @@ export const billingJson: BillingV2Type = { products: [ { name: 'Product analytics', - headline: null, - description: 'Trends, funnels, path analysis, CDP + more.', + headline: 'Product analytics with autocapture', + description: + 'A comprehensive product analytics platform built to natively work with session replay, feature flags, A/B testing, and surveys.', price_description: null, usage_key: 'events', image_url: 'https://posthog.com/images/products/product-analytics/product-analytics.png', - screenshot_url: null, + screenshot_url: 'https://posthog.com/images/products/product-analytics/screenshot-product-analytics.png', icon_key: 'IconGraph', docs_url: 'https://posthog.com/docs/product-analytics', subscribed: true, @@ -29,9 +30,10 @@ export const billingJson: BillingV2Type = { { plan_key: 'free-20230117', product_key: 'product_analytics', - name: 'Product analytics', - description: 'Trends, funnels, path analysis, CDP + more.', - image_url: 'https://posthog.com/images/product/product-icons/product-analytics.svg', + 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', note: null, unit: 'event', @@ -49,7 +51,8 @@ export const billingJson: BillingV2Type = { { key: 'funnels', name: 'Funnels', - description: 'Visualize user dropoff between a sequence of events.', + description: + 'Visualize user dropoff between a sequence of events. See conversion rate over time, use flexible step ordering, set exclusion steps, and more.', unit: null, limit: null, note: null, @@ -64,7 +67,7 @@ export const billingJson: BillingV2Type = { }, { key: 'paths', - name: 'Paths', + name: 'User paths', description: 'Limited paths excludes: customizing path insights by setting the maximum number of paths, number of people on each path, how path names appear', unit: null, @@ -83,13 +86,16 @@ export const billingJson: BillingV2Type = { tiers: null, current_plan: false, included_if: null, + contact_support: null, + unit_amount_usd: null, }, { plan_key: 'paid-20240111', product_key: 'product_analytics', - name: 'Product analytics', - description: 'Trends, funnels, path analysis, CDP + more.', - image_url: 'https://posthog.com/images/product/product-icons/product-analytics.svg', + 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', note: null, unit: 'event', @@ -107,7 +113,8 @@ export const billingJson: BillingV2Type = { { key: 'funnels', name: 'Funnels', - description: 'Visualize user dropoff between a sequence of events.', + description: + 'Visualize user dropoff between a sequence of events. See conversion rate over time, use flexible step ordering, set exclusion steps, and more.', unit: null, limit: null, note: null, @@ -122,7 +129,7 @@ export const billingJson: BillingV2Type = { }, { key: 'paths', - name: 'Paths', + name: 'User paths', description: 'Limited paths excludes: customizing path insights by setting the maximum number of paths, number of people on each path, how path names appear', unit: null, @@ -147,32 +154,6 @@ export const billingJson: BillingV2Type = { limit: null, note: null, }, - { - key: 'advanced_permissions', - name: 'Dashboard permissions', - description: - 'Restrict access to dashboards within the organization to only those who need it.', - unit: null, - limit: null, - note: null, - }, - { - key: 'team_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', @@ -182,19 +163,11 @@ export const billingJson: BillingV2Type = { 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 cohorts', + name: 'Lifecycle', description: - 'Group users based on their long term behavior, such as whether they frequently performed an event, or have recently stopped performing an event.', + 'Discover how your active users break down, highlighting those who have recently stopped being active or those who have just become active for the first time.', unit: null, limit: null, note: null, @@ -275,6 +248,8 @@ export const billingJson: BillingV2Type = { ], current_plan: true, included_if: null, + contact_support: null, + unit_amount_usd: null, }, ], type: 'product_analytics', @@ -349,7 +324,7 @@ export const billingJson: BillingV2Type = { current_amount_usd_before_addons: '0.00', current_amount_usd: '0.00', current_usage: 882128, - usage_limit: 3624548, + usage_limit: 882128, has_exceeded_limit: false, percentage_usage: 0.4423939206, projected_usage: 7000000, @@ -443,7 +418,7 @@ export const billingJson: BillingV2Type = { { 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', @@ -529,6 +504,8 @@ export const billingJson: BillingV2Type = { ], current_plan: false, included_if: null, + contact_support: null, + unit_amount_usd: null, }, ], contact_support: false, @@ -620,7 +597,7 @@ export const billingJson: BillingV2Type = { { 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, @@ -706,6 +683,8 @@ export const billingJson: BillingV2Type = { ], current_plan: false, included_if: null, + contact_support: null, + unit_amount_usd: null, }, ], contact_support: false, @@ -780,31 +759,6 @@ export const billingJson: BillingV2Type = { icon_key: 'IconNotification', type: 'secondary', }, - { - key: 'team_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: 'advanced_permissions', - 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', @@ -826,14 +780,6 @@ export const billingJson: BillingV2Type = { 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', @@ -899,13 +845,13 @@ export const billingJson: BillingV2Type = { }, { name: 'Session replay', - headline: null, + headline: 'Watch how users experience your app', description: - 'Searchable recordings of people using your app or website with console logs and behavioral bucketing.', + 'Session replay helps you diagnose issues and understand user behavior in your product or website.', price_description: null, usage_key: 'recordings', - image_url: 'https://posthog.com/images/product/product-icons/session-replay.svg', - screenshot_url: null, + image_url: 'https://posthog.com/images/products/session-replay/session-replay.png', + screenshot_url: 'https://posthog.com/images/products/session-replay/screenshot-session-replay.png', icon_key: 'IconRewindPlay', docs_url: 'https://posthog.com/docs/session-replay', subscribed: true, @@ -913,10 +859,10 @@ export const billingJson: BillingV2Type = { { plan_key: 'free-20231218', product_key: 'session_replay', - name: 'Session replay', + name: 'Free', description: - 'Searchable recordings of people using your app or website with console logs and behavioral bucketing.', - image_url: 'https://posthog.com/images/product/product-icons/session-replay.svg', + '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', note: null, unit: 'recording', @@ -925,7 +871,7 @@ export const billingJson: BillingV2Type = { { key: 'console_logs', name: 'Console logs', - description: "Diagnose issues by inspecting errors in the user's network console", + description: "Debug issues faster by browsing the user's console.", unit: null, limit: null, note: null, @@ -941,9 +887,8 @@ export const billingJson: BillingV2Type = { }, { key: 'session_replay_network_payloads', - name: 'Network payload capture', - description: - 'Capture and analyze network requests and response payloads and headers for each session recording.', + name: 'Network monitor', + description: 'Analyze performance and network calls.', unit: null, limit: null, note: null, @@ -967,7 +912,7 @@ export const billingJson: BillingV2Type = { }, { key: 'replay_mask_sensitive_data', - name: 'Mask sensitive data', + name: 'Block sensitive data', description: 'Disable capturing data from any DOM element with HTML attributes or a customizable config.', unit: null, @@ -984,9 +929,8 @@ export const billingJson: BillingV2Type = { }, { key: 'replay_product_analytics_integration', - name: 'Product analytics integration', - description: - 'Jump into a playlist of session recordings directly from any time series in a graph. See when events happen in your recording timeline.', + name: 'Event timeline', + description: "See a history of everything that happened in a user's session.", unit: null, limit: null, note: null, @@ -1044,14 +988,16 @@ export const billingJson: BillingV2Type = { tiers: null, current_plan: false, included_if: null, + contact_support: null, + unit_amount_usd: null, }, { plan_key: 'paid-20231218', product_key: 'session_replay', - name: 'Session replay', + name: 'Paid', description: - 'Searchable recordings of people using your app or website with console logs and behavioral bucketing.', - image_url: 'https://posthog.com/images/product/product-icons/session-replay.svg', + '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', note: null, unit: 'recording', @@ -1060,7 +1006,7 @@ export const billingJson: BillingV2Type = { { key: 'console_logs', name: 'Console logs', - description: "Diagnose issues by inspecting errors in the user's network console", + description: "Debug issues faster by browsing the user's console.", unit: null, limit: null, note: null, @@ -1085,17 +1031,16 @@ export const billingJson: BillingV2Type = { }, { key: 'session_replay_network_payloads', - name: 'Network payload capture', - description: - 'Capture and analyze network requests and response payloads and headers for each session recording.', + name: 'Network monitor', + description: 'Analyze performance and network calls.', unit: null, limit: null, note: null, }, { key: 'recordings_file_export', - name: 'Recordings file export', - description: 'Save session recordings as a file to your local filesystem.', + name: 'Download recordings', + description: 'Retain recordings beyond data retention limits.', unit: null, limit: null, note: null, @@ -1110,7 +1055,7 @@ export const billingJson: BillingV2Type = { }, { key: 'replay_mask_sensitive_data', - name: 'Mask sensitive data', + name: 'Block sensitive data', description: 'Disable capturing data from any DOM element with HTML attributes or a customizable config.', unit: null, @@ -1127,9 +1072,8 @@ export const billingJson: BillingV2Type = { }, { key: 'replay_product_analytics_integration', - name: 'Product analytics integration', - description: - 'Jump into a playlist of session recordings directly from any time series in a graph. See when events happen in your recording timeline.', + name: 'Event timeline', + description: "See a history of everything that happened in a user's session.", unit: null, limit: null, note: null, @@ -1191,7 +1135,7 @@ export const billingJson: BillingV2Type = { up_to: 5000, current_amount_usd: '0.00', current_usage: 0, - projected_usage: 0, + projected_usage: null, projected_amount_usd: null, }, { @@ -1242,6 +1186,8 @@ export const billingJson: BillingV2Type = { ], current_plan: true, included_if: null, + contact_support: null, + unit_amount_usd: null, }, ], type: 'session_replay', @@ -1280,7 +1226,7 @@ export const billingJson: BillingV2Type = { up_to: 150000, current_amount_usd: '0.00', current_usage: 0, - projected_usage: 100000, + projected_usage: 10000, projected_amount_usd: '270.00', }, { @@ -1304,7 +1250,7 @@ export const billingJson: BillingV2Type = { ], tiered: true, unit_amount_usd: null, - current_amount_usd_before_addons: null, + current_amount_usd_before_addons: '0.00', current_amount_usd: '403.07', current_usage: 16022, usage_limit: 100000, @@ -1469,12 +1415,13 @@ export const billingJson: BillingV2Type = { }, { name: 'Feature flags & A/B testing', - headline: null, - description: 'Safely roll out new features and run experiments on changes.', + headline: 'Safely roll out features and A/B tests to specific users or groups', + description: + 'Test changes with small groups of users before rolling out wider. Analyze usage with product analytics and session replay.', price_description: null, usage_key: 'feature_flag_requests', - image_url: 'https://posthog.com/images/product/product-icons/feature-flags.svg', - screenshot_url: null, + image_url: 'https://posthog.com/images/products/feature-flags/feature-flags.png', + screenshot_url: 'https://posthog.com/images/products/feature-flags/screenshot-feature-flags.png', icon_key: 'IconToggle', docs_url: 'https://posthog.com/docs/feature-flags', subscribed: false, @@ -1482,9 +1429,10 @@ export const billingJson: BillingV2Type = { { plan_key: 'free-20230117', product_key: 'feature_flags', - name: 'Feature flags & A/B testing', - description: 'Safely roll out new features and run experiments on changes.', - image_url: 'https://posthog.com/images/product/product-icons/feature-flags.svg', + 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', note: null, unit: 'request', @@ -1498,6 +1446,15 @@ export const billingJson: BillingV2Type = { limit: null, note: null, }, + { + key: 'multivariate_flags', + name: 'Multivariate feature flags & experiments', + description: + 'Create three or more variants of a feature flag to test or release different versions of a feature.', + unit: null, + limit: null, + note: null, + }, { key: 'persist_flags_cross_authentication', name: 'Persist flags across authentication', @@ -1509,9 +1466,9 @@ export const billingJson: BillingV2Type = { }, { key: 'feature_flag_payloads', - name: 'Payloads', + name: 'Test changes without code', description: - 'Send additional pieces of information (any valid JSON) to your app when a flag is matched for a user.', + 'Use JSON payloads to change text, visuals, or entire blocks of code without subsequent deployments.', unit: null, limit: null, note: null, @@ -1520,7 +1477,7 @@ export const billingJson: BillingV2Type = { key: 'multiple_release_conditions', name: 'Multiple release conditions', description: - 'Target multiple groups of users with different release conditions for the same feature flag.', + 'Customize your rollout strategy by user or group properties, cohort, or trafic percentage.', unit: null, limit: null, note: null, @@ -1561,6 +1518,41 @@ export const billingJson: BillingV2Type = { limit: null, note: null, }, + { + key: 'experimentation', + name: 'A/B testing', + description: 'Test changes to your product and evaluate the impacts those changes make.', + unit: null, + limit: null, + note: null, + }, + { + key: 'funnel_experiments', + name: 'Funnel & trend experiments', + description: + 'Measure the impact of a change on a aggregate values or a series of events, like a signup flow.', + unit: null, + limit: null, + note: null, + }, + { + key: 'secondary_metrics', + name: 'Secondary experiment metrics', + description: + 'Track additional metrics to see how your experiment affects other parts of your app or different flows.', + unit: null, + limit: null, + note: null, + }, + { + key: 'statistical_analysis', + name: 'Statistical analysis', + description: + "Get a statistical analysis of your experiment results to see if the results are significant, or if they're likely just due to chance.", + unit: null, + limit: null, + note: null, + }, { key: 'feature_flags_data_retention', name: 'Data retention', @@ -1573,13 +1565,16 @@ export const billingJson: BillingV2Type = { tiers: null, current_plan: true, included_if: null, + contact_support: null, + unit_amount_usd: null, }, { plan_key: 'paid-20230623', product_key: 'feature_flags', - name: 'Feature flags & A/B testing', - description: 'Safely roll out new features and run experiments on changes.', - image_url: 'https://posthog.com/images/product/product-icons/feature-flags.svg', + 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', note: null, unit: 'request', @@ -1613,9 +1608,9 @@ export const billingJson: BillingV2Type = { }, { key: 'feature_flag_payloads', - name: 'Payloads', + name: 'Test changes without code', description: - 'Send additional pieces of information (any valid JSON) to your app when a flag is matched for a user.', + 'Use JSON payloads to change text, visuals, or entire blocks of code without subsequent deployments.', unit: null, limit: null, note: null, @@ -1624,7 +1619,7 @@ export const billingJson: BillingV2Type = { key: 'multiple_release_conditions', name: 'Multiple release conditions', description: - 'Target multiple groups of users with different release conditions for the same feature flag.', + 'Customize your rollout strategy by user or group properties, cohort, or trafic percentage.', unit: null, limit: null, note: null, @@ -1673,15 +1668,6 @@ export const billingJson: BillingV2Type = { limit: null, note: null, }, - { - key: 'group_experiments', - name: 'Group experiments', - description: - 'Target experiments to specific groups of users so everyone in the same group gets the same variant.', - unit: null, - limit: null, - note: null, - }, { key: 'funnel_experiments', name: 'Funnel & trend experiments', @@ -1709,6 +1695,24 @@ export const billingJson: BillingV2Type = { limit: null, note: null, }, + { + key: 'group_experiments', + name: 'Group experiments', + description: + 'Target experiments to specific groups of users so everyone in the same group gets the same variant.', + unit: null, + limit: null, + note: null, + }, + { + key: 'multiple_environments', + name: 'Multi-environment support', + description: + 'Test flags in local development or staging by using the same flag key across PostHog projects.', + unit: null, + limit: null, + note: null, + }, { key: 'feature_flags_data_retention', name: 'Data retention', @@ -1767,6 +1771,8 @@ export const billingJson: BillingV2Type = { ], current_plan: false, included_if: null, + contact_support: null, + unit_amount_usd: null, }, ], type: 'feature_flags', @@ -1964,12 +1970,13 @@ export const billingJson: BillingV2Type = { }, { name: 'Surveys', - headline: null, - description: 'Collect feedback from your users. Multiple choice, rating, open text, and more.', + headline: 'Ask anything with no-code surveys', + description: + 'Build in-app popups with freeform text responses, multiple choice, NPS, ratings, and emoji reactions. Or use the API for complete control.', price_description: null, usage_key: 'survey_responses', - image_url: 'https://posthog.com/images/product/product-icons/surveys.svg', - screenshot_url: null, + image_url: 'https://posthog.com/images/products/surveys/surveys.png', + screenshot_url: 'https://posthog.com/images/products/surveys/screenshot-surveys.png', icon_key: 'IconMessage', docs_url: 'https://posthog.com/docs/surveys', subscribed: false, @@ -1977,9 +1984,10 @@ export const billingJson: BillingV2Type = { { plan_key: 'free-20230928', product_key: 'surveys', - name: 'Surveys', - description: 'Collect feedback from your users. Multiple choice, rating, open text, and more.', - image_url: 'https://posthog.com/images/product/product-icons/surveys.svg', + 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', note: null, unit: 'survey response', @@ -2004,8 +2012,8 @@ export const billingJson: BillingV2Type = { }, { key: 'surveys_user_targeting', - name: 'User property targeting', - description: 'Target users based on any of their user properties.', + name: 'Advanced user targeting', + description: 'Target by URL, user property, or feature flag when used with Feature flags.', unit: null, limit: null, note: null, @@ -2021,15 +2029,17 @@ export const billingJson: BillingV2Type = { { key: 'surveys_api_mode', name: 'API mode', - description: 'Create surveys via the API.', + description: + 'Using PostHog.js? No more code required. But if want to create your own UI, we have a full API.', unit: null, limit: null, note: null, }, { key: 'surveys_results_analysis', - name: 'Results analysis', - description: 'Analyze your survey results including completion rates and drop offs.', + name: 'Aggregated results', + description: + 'See feedback summarized and broken down per response, plus completion rates and drop offs.', unit: null, limit: null, note: null, @@ -2055,13 +2065,16 @@ export const billingJson: BillingV2Type = { tiers: null, current_plan: true, included_if: null, + contact_support: null, + unit_amount_usd: null, }, { plan_key: 'paid-20230928', product_key: 'surveys', - name: 'Surveys', - description: 'Collect feedback from your users. Multiple choice, rating, open text, and more.', - image_url: 'https://posthog.com/images/product/product-icons/surveys.svg', + 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', note: null, unit: 'survey response', @@ -2087,15 +2100,15 @@ export const billingJson: BillingV2Type = { { key: 'surveys_multiple_questions', name: 'Multiple questions', - description: 'Create multiple questions in a single survey.', + description: 'Ask up to 10 questions in a single survey.', unit: null, limit: null, note: null, }, { key: 'surveys_user_targeting', - name: 'User property targeting', - description: 'Target users based on any of their user properties.', + name: 'Advanced user targeting', + description: 'Target by URL, user property, or feature flag when used with Feature flags.', unit: null, limit: null, note: null, @@ -2128,15 +2141,17 @@ export const billingJson: BillingV2Type = { { key: 'surveys_api_mode', name: 'API mode', - description: 'Create surveys via the API.', + description: + 'Using PostHog.js? No more code required. But if want to create your own UI, we have a full API.', unit: null, limit: null, note: null, }, { key: 'surveys_results_analysis', - name: 'Results analysis', - description: 'Analyze your survey results including completion rates and drop offs.', + name: 'Aggregated results', + description: + 'See feedback summarized and broken down per response, plus completion rates and drop offs.', unit: null, limit: null, note: null, @@ -2217,6 +2232,8 @@ export const billingJson: BillingV2Type = { ], current_plan: false, included_if: null, + contact_support: null, + unit_amount_usd: null, }, ], type: 'surveys', @@ -2388,7 +2405,7 @@ export const billingJson: BillingV2Type = { { 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', @@ -2442,11 +2459,13 @@ export const billingJson: BillingV2Type = { tiers: null, current_plan: false, 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', @@ -2508,6 +2527,8 @@ export const billingJson: BillingV2Type = { tiers: null, current_plan: true, included_if: 'has_subscription', + contact_support: null, + unit_amount_usd: null, }, ], type: 'integrations', @@ -2595,7 +2616,7 @@ export const billingJson: BillingV2Type = { { 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', @@ -2653,22 +2674,24 @@ export const billingJson: BillingV2Type = { 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: false, 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', @@ -2697,9 +2720,9 @@ export const billingJson: BillingV2Type = { 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', + unit: 'projects', + limit: 2, + note: null, }, { key: 'api_access', @@ -2718,69 +2741,483 @@ export const billingJson: BillingV2Type = { note: null, }, { - key: 'project_based_permissioning', - name: 'Project permissions', - description: 'Restrict access to data within the organization to only those who need it.', + 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: 'white_labelling', - name: 'White labeling', - description: 'Use your own branding in your PostHog organization.', + 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: 'community_support', - name: 'Community support', - description: 'Get help from other users and PostHog team members in our Community forums.', + key: '2fa', + name: '2FA', + description: 'Secure your PostHog account with two-factor authentication.', unit: null, limit: null, note: null, }, + ], + tiers: null, + current_plan: true, + 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', + note: null, + unit: null, + free_allocation: null, + features: [ { - key: 'dedicated_support', - name: 'Slack (dedicated channel)', + 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: - 'Get help directly from our support team in a dedicated Slack channel shared between you and the PostHog team.', + 'Create silos of data within PostHog. All data belongs to a single project and all queries are project-specific.', unit: null, limit: null, - note: '$2k/month spend or above', + note: 'Unlimited', }, { - key: 'email_support', - name: 'Direct access to engineers', + 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: 'sso_enforcement', + name: 'Enforce SSO login', description: - 'Get help directly from our product engineers via email. No wading through multiple support people before you get help.', + 'Users can only sign up and log in to your PostHog organization with your specified SSO provider.', unit: null, limit: null, 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, }, { - key: 'security_assessment', - name: 'Security assessment', - description: 'Security assessment', + key: '2fa_enforcement', + name: 'Enforce 2FA', + description: 'Require all users in your organization to enable two-factor authentication.', unit: null, limit: null, note: null, }, - ], - tiers: null, - current_plan: true, - included_if: 'has_subscription', - }, - ], + { + 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', + description: 'Restrict access to data within the organization to only those who need it.', + 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: '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 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, + }, + { + 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: null, + }, + { + key: 'priority_support', + name: 'Priority support', + description: 'Get help from our team faster than other customers.', + unit: null, + limit: null, + note: null, + }, + { + key: 'security_assessment', + name: 'Security assessment', + description: 'Security assessment', + 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: null, + contact_support: true, + unit_amount_usd: null, + }, + ], type: 'platform_and_support', free_allocation: 0, tiers: null, @@ -2796,7 +3233,7 @@ export const billingJson: BillingV2Type = { projected_amount_usd: null, unit: null, addons: [], - contact_support: true, + contact_support: false, inclusion_only: true, features: [ { @@ -2832,6 +3269,14 @@ export const billingJson: BillingV2Type = { 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.', + images: null, + icon_key: null, + type: null, + }, { key: 'role_based_access', name: 'Role-based access', @@ -2842,17 +3287,17 @@ export const billingJson: BillingV2Type = { 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: 'project_based_permissioning', + name: 'Project permissions', + description: 'Restrict access to data within the organization to only those who need it.', images: null, icon_key: null, type: null, }, { - key: 'project_based_permissioning', - name: 'Project permissions', - description: 'Restrict access to data within the organization to only those who need it.', + 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, @@ -2874,10 +3319,26 @@ export const billingJson: BillingV2Type = { 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, @@ -2892,16 +3353,16 @@ export const billingJson: BillingV2Type = { }, { key: 'dedicated_support', - name: 'Slack (dedicated channel)', + name: 'Dedicated account manager', description: - 'Get help directly from our support team in a dedicated Slack channel shared between you and the PostHog team.', + '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, @@ -2909,16 +3370,16 @@ export const billingJson: BillingV2Type = { 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, @@ -2926,7 +3387,7 @@ export const billingJson: BillingV2Type = { }, { 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, @@ -2973,11 +3434,62 @@ export const billingJson: BillingV2Type = { 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, + }, ], }, ], - custom_limits_usd: { - session_replay: '700', - product_analytics: '550', - }, + custom_limits_usd: {}, + stripe_portal_url: + 'https://billing.stripe.com/p/session/test_YWNjdF8xSElNRERFdUlhdFJYU2R6LF9QaEVJR3VyemlvMDZzRzdiQXZrc1AxSjNXZk1BellP0100ZsforDQG', } diff --git a/frontend/src/scenes/UpgradeModal.tsx b/frontend/src/scenes/UpgradeModal.tsx index 0a3c7bd625800..bea905c356b1d 100644 --- a/frontend/src/scenes/UpgradeModal.tsx +++ b/frontend/src/scenes/UpgradeModal.tsx @@ -17,10 +17,10 @@ export function UpgradeModal(): JSX.Element { isGrandfathered={upgradeModalIsGrandfathered ?? undefined} background={false} > - <> +
    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/billing/Billing.stories.tsx b/frontend/src/scenes/billing/Billing.stories.tsx index ff420d9db1c51..7ccd862eb111a 100644 --- a/frontend/src/scenes/billing/Billing.stories.tsx +++ b/frontend/src/scenes/billing/Billing.stories.tsx @@ -116,6 +116,7 @@ export const BillingUnsubscribeModal_DataPipelines = (): JSX.Element => { projected_amount_usd: '0', plans: [], usage_key: '', + contact_support: false, }, ] 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..a185b4df0277f 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 ( - {plan.current_plan ? 'Current plan' @@ -182,8 +186,18 @@ export const PlanComparison = ({ i >= 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/data-warehouse/viewLinkLogic.tsx b/frontend/src/scenes/data-warehouse/viewLinkLogic.tsx index 34e63deaf130d..414287bdcaa36 100644 --- a/frontend/src/scenes/data-warehouse/viewLinkLogic.tsx +++ b/frontend/src/scenes/data-warehouse/viewLinkLogic.tsx @@ -206,8 +206,9 @@ export const viewLinkLogic = kea([ }, ], selectedSourceTable: [ - (s) => [s.selectedSourceTableName, s.tables], - (selectedSourceTableName, tables) => tables.find((row) => row.name === selectedSourceTableName), + (s) => [s.selectedSourceTableName, s.tables, s.savedQueries], + (selectedSourceTableName, tables, savedQueries) => + [...tables, ...savedQueries].find((row) => row.name === selectedSourceTableName), ], selectedJoiningTable: [ (s) => [s.selectedJoiningTableName, s.tables], @@ -234,12 +235,17 @@ export const viewLinkLogic = kea([ }, ], tableOptions: [ - (s) => [s.tables], - (tables) => - tables.map((table) => ({ + (s) => [s.tables, s.savedQueries], + (tables, savedQueries) => [ + ...tables.map((table) => ({ value: table.name, label: table.name, })), + ...savedQueries.map((query) => ({ + value: query.name, + label: query.name, + })), + ], ], sourceTableKeys: [ (s) => [s.selectedSourceTable], diff --git a/frontend/src/scenes/onboarding/OnboardingBillingStep.tsx b/frontend/src/scenes/onboarding/OnboardingBillingStep.tsx index be78629f06f26..a5ac9fc397506 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 : ( - { reportBillingUpgradeClicked(product.type) }} + data-attr="onboarding-subscribe-button" > - 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 +76,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}.

    @@ -71,7 +94,11 @@ export const OnboardingBillingStep = ({
    - setShowPlanComp(!showPlanComp)}> + setShowPlanComp(!showPlanComp)} + > {showPlanComp ? 'Hide' : 'Show'} plans {currentPlan?.initial_billing_limit && ( diff --git a/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx b/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx index 5402f8fe7d728..0daad6c18d479 100644 --- a/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx +++ b/frontend/src/scenes/onboarding/OnboardingProductIntroduction.tsx @@ -96,7 +96,7 @@ const GetStartedButton = ({ product }: { product: BillingProductV2Type }): JSX.E {(!hasSnippetEvents || multiInstallProducts.includes(product.type as ProductKey)) && ( { setTeamPropertiesForProduct(product.type as ProductKey) reportOnboardingProductSelected( diff --git a/frontend/src/scenes/onboarding/OnboardingStep.tsx b/frontend/src/scenes/onboarding/OnboardingStep.tsx index c4cd544e7a38d..ddb081b343919 100644 --- a/frontend/src/scenes/onboarding/OnboardingStep.tsx +++ b/frontend/src/scenes/onboarding/OnboardingStep.tsx @@ -96,6 +96,7 @@ export const OnboardingStep = ({ onSkip && onSkip() !hasNextStep ? completeOnboarding() : goToNextStep() }} + data-attr="onboarding-skip-button" > Skip {!hasNextStep ? 'and finish' : 'for now'} @@ -106,6 +107,7 @@ export const OnboardingStep = ({ { continueAction && continueAction() !hasNextStep ? completeOnboarding() : goToNextStep() diff --git a/frontend/src/scenes/onboarding/onboardingLogic.tsx b/frontend/src/scenes/onboarding/onboardingLogic.tsx index f6b597b74414a..8857c73b7fe42 100644 --- a/frontend/src/scenes/onboarding/onboardingLogic.tsx +++ b/frontend/src/scenes/onboarding/onboardingLogic.tsx @@ -1,7 +1,6 @@ import { actions, connect, kea, listeners, path, props, reducers, selectors } from 'kea' -import { actionToUrl, combineUrl, router, urlToAction } from 'kea-router' -import { FEATURE_FLAGS } from 'lib/constants' -import { featureFlagLogic, FeatureFlagsSet } from 'lib/logic/featureFlagLogic' +import { actionToUrl, router, urlToAction } from 'kea-router' +import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { billingLogic } from 'scenes/billing/billingLogic' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' @@ -62,13 +61,10 @@ export const stepKeyToTitle = (stepKey?: OnboardingStepKey): undefined | string export type AllOnboardingSteps = OnboardingStep[] export type OnboardingStep = JSX.Element -export const getProductUri = (productKey: ProductKey, featureFlags?: FeatureFlagsSet): string => { +export const getProductUri = (productKey: ProductKey): string => { switch (productKey) { case ProductKey.PRODUCT_ANALYTICS: - return featureFlags && - featureFlags[FEATURE_FLAGS.REDIRECT_INSIGHT_CREATION_PRODUCT_ANALYTICS_ONBOARDING] === 'test' - ? urls.insightNew() - : combineUrl(urls.insights(), {}, { panel: 'activation' }).url + return urls.insightNew() case ProductKey.SESSION_REPLAY: return urls.replay() case ProductKey.FEATURE_FLAGS: @@ -168,9 +164,9 @@ export const onboardingLogic = kea([ }, ], onCompleteOnboardingRedirectUrl: [ - (s) => [s.featureFlags, s.productKey], - (featureFlags: FeatureFlagsSet, productKey: string | null) => { - return productKey ? getProductUri(productKey as ProductKey, featureFlags) : urls.default() + (s) => [s.productKey], + (productKey: string | null) => { + return productKey ? getProductUri(productKey as ProductKey) : urls.default() }, ], totalOnboardingSteps: [ diff --git a/frontend/src/scenes/onboarding/sdks/SDKs.tsx b/frontend/src/scenes/onboarding/sdks/SDKs.tsx index d885e94ea6fcf..33555a1f17ca9 100644 --- a/frontend/src/scenes/onboarding/sdks/SDKs.tsx +++ b/frontend/src/scenes/onboarding/sdks/SDKs.tsx @@ -86,6 +86,7 @@ export function SDKs({ ) : ( <> : null} type="primary" status="alt" diff --git a/frontend/src/scenes/products/Products.tsx b/frontend/src/scenes/products/Products.tsx index c1f7311425a05..3eade1085f70e 100644 --- a/frontend/src/scenes/products/Products.tsx +++ b/frontend/src/scenes/products/Products.tsx @@ -5,7 +5,6 @@ import { useActions, useValues } from 'kea' import { router } from 'kea-router' import { LemonCard } from 'lib/lemon-ui/LemonCard/LemonCard' import { Spinner } from 'lib/lemon-ui/Spinner' -import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import { billingLogic } from 'scenes/billing/billingLogic' import { getProductUri, onboardingLogic } from 'scenes/onboarding/onboardingLogic' @@ -44,7 +43,6 @@ export function ProductCard({ className?: string }): JSX.Element { const { currentTeam } = useValues(teamLogic) - const { featureFlags } = useValues(featureFlagLogic) const { setIncludeIntro } = useActions(onboardingLogic) const { user } = useValues(userLogic) const { reportOnboardingProductSelected } = useActions(eventUsageLogic) @@ -77,8 +75,9 @@ export function ProductCard({ className="relative" onClick={(e) => { e.stopPropagation() - router.actions.push(getProductUri(product.type as ProductKey, featureFlags)) + router.actions.push(getProductUri(product.type as ProductKey)) }} + data-attr={`return-to-${product.type}`} > 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 {matchedRecordings?.length ? matchedRecordings.map((recording, i) => ( - <> + -
  • +
  • { recording.session_id && @@ -431,7 +430,7 @@ export function ActorRow({ actor, onOpenRecording, propertiesTimelineFilter }: A
  • - + )) : null} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 9a9535ab5d6a0..74d8ca7db1748 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,10 @@ export enum AvailableFeature { PRODUCT_ANALYTICS_SQL_QUERIES = 'product_analytics_sql_queries', TWOFA_ENFORCEMENT = '2fa_enforcement', AUDIT_LOGS = 'audit_logs', + HIPAA_BAA = 'hipaa_baa', + CUSTOMM_MSA = 'custom_msa', + TWOFA = '2fa', + PRIORITY_SUPPORT = 'priority_support', } type AvailableFeatureUnion = `${AvailableFeature}` @@ -1401,7 +1406,7 @@ export interface BillingProductV2Type { unit: string | null unit_amount_usd: string | null plans: BillingV2PlanType[] - contact_support: boolean + contact_support: boolean | null inclusion_only: any features: BillingV2FeatureType[] addons: BillingProductV2AddonType[] @@ -1422,7 +1427,7 @@ export interface BillingProductV2AddonType { subscribed: boolean // sometimes addons are included with the base product, but they aren't subscribed individually included_with_main_product?: boolean - contact_support?: boolean + contact_support: boolean | null unit: string | null unit_amount_usd: string | null current_amount_usd: string | null @@ -1475,10 +1480,10 @@ export interface BillingV2PlanType { product_key: ProductKeyUnion current_plan?: boolean | null tiers?: BillingV2TierType[] | null - unit_amount_usd?: string + unit_amount_usd: string | null included_if?: 'no_active_subscription' | 'has_subscription' | null initial_billing_limit?: number - contact_support?: boolean + contact_support: boolean | null } export interface PlanInterface { 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 6271ebd783fc3..73998505bb822 100644 --- a/posthog/api/capture.py +++ b/posthog/api/capture.py @@ -316,6 +316,11 @@ def drop_events_over_quota(token: str, events: List[Any]) -> List[Any]: return results +def lib_version_from_query_params(request) -> str: + # url has a ver parameter from posthog-js + return request.GET.get("ver", "unknown") + + @csrf_exempt @timed("posthog_cloud_event_endpoint") def get_event(request): @@ -494,6 +499,8 @@ def get_event(request): try: if replay_events: + lib_version = lib_version_from_query_params(request) + alternative_replay_events = preprocess_replay_events_for_blob_ingestion( replay_events, settings.SESSION_RECORDING_KAFKA_MAX_REQUEST_SIZE_BYTES ) @@ -515,6 +522,7 @@ def get_event(request): sent_at, event_uuid, token, + extra_headers=[("lib_version", lib_version)], ) ) @@ -565,10 +573,24 @@ def parse_event(event): return event -def capture_internal(event, distinct_id, ip, site_url, now, sent_at, event_uuid=None, token=None, historical=False): +def capture_internal( + event, + distinct_id, + ip, + site_url, + now, + sent_at, + event_uuid=None, + token=None, + historical=False, + extra_headers: List[Tuple[str, str]] | None = None, +): if event_uuid is None: event_uuid = UUIDT() + if extra_headers is None: + extra_headers = [] + parsed_event = build_kafka_event_data( distinct_id=distinct_id, ip=ip, @@ -589,7 +611,7 @@ def capture_internal(event, distinct_id, ip, site_url, now, sent_at, event_uuid= session_id = event["properties"]["$session_id"] headers = [ ("token", token), - ] + ] + extra_headers overflowing = False if token in settings.REPLAY_OVERFLOW_FORCED_TOKENS: @@ -605,7 +627,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() @@ -641,7 +663,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/api/test/__snapshots__/test_cohort.ambr b/posthog/api/test/__snapshots__/test_cohort.ambr index a336dd016d093..9789176ddd21b 100644 --- a/posthog/api/test/__snapshots__/test_cohort.ambr +++ b/posthog/api/test/__snapshots__/test_cohort.ambr @@ -1,7 +1,7 @@ # serializer version: 1 # name: TestCohort.test_async_deletion_of_cohort ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ SELECT count(DISTINCT person_id) FROM cohortpeople WHERE team_id = 2 @@ -11,7 +11,7 @@ # --- # name: TestCohort.test_async_deletion_of_cohort.1 ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ INSERT INTO cohortpeople SELECT id, 2 as cohort_id, @@ -84,7 +84,7 @@ # --- # name: TestCohort.test_async_deletion_of_cohort.2 ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ SELECT count(DISTINCT person_id) FROM cohortpeople WHERE team_id = 2 @@ -94,7 +94,7 @@ # --- # name: TestCohort.test_async_deletion_of_cohort.3 ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.clear_stale_cohort */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.clear_stale_cohort */ SELECT count() FROM cohortpeople WHERE team_id = 2 @@ -104,7 +104,7 @@ # --- # name: TestCohort.test_async_deletion_of_cohort.4 ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ SELECT count(DISTINCT person_id) FROM cohortpeople WHERE team_id = 2 @@ -114,7 +114,7 @@ # --- # name: TestCohort.test_async_deletion_of_cohort.5 ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ INSERT INTO cohortpeople SELECT id, 2 as cohort_id, @@ -148,7 +148,7 @@ # --- # name: TestCohort.test_async_deletion_of_cohort.6 ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.calculate_cohort_ch */ SELECT count(DISTINCT person_id) FROM cohortpeople WHERE team_id = 2 @@ -158,7 +158,7 @@ # --- # name: TestCohort.test_async_deletion_of_cohort.7 ''' - /* user_id:125 celery:posthog.tasks.calculate_cohort.clear_stale_cohort */ + /* user_id:123 celery:posthog.tasks.calculate_cohort.clear_stale_cohort */ SELECT count() FROM cohortpeople WHERE team_id = 2 diff --git a/posthog/api/test/__snapshots__/test_feature_flag.ambr b/posthog/api/test/__snapshots__/test_feature_flag.ambr index bc91431d76149..fbf8995d66e68 100644 --- a/posthog/api/test/__snapshots__/test_feature_flag.ambr +++ b/posthog/api/test/__snapshots__/test_feature_flag.ambr @@ -1739,7 +1739,7 @@ # --- # name: TestFeatureFlag.test_creating_static_cohort.14 ''' - /* user_id:199 celery:posthog.tasks.calculate_cohort.insert_cohort_from_feature_flag */ + /* user_id:197 celery:posthog.tasks.calculate_cohort.insert_cohort_from_feature_flag */ SELECT count(DISTINCT person_id) FROM person_static_cohort WHERE team_id = 2 diff --git a/posthog/api/test/__snapshots__/test_query.ambr b/posthog/api/test/__snapshots__/test_query.ambr index bdb245bc10292..628038e741728 100644 --- a/posthog/api/test/__snapshots__/test_query.ambr +++ b/posthog/api/test/__snapshots__/test_query.ambr @@ -157,7 +157,7 @@ # --- # name: TestQuery.test_full_hogql_query_async ''' - /* user_id:466 celery:posthog.tasks.tasks.process_query_task */ + /* user_id:464 celery:posthog.tasks.tasks.process_query_task */ SELECT events.uuid AS uuid, events.event AS event, events.properties AS properties, diff --git a/posthog/api/test/test_capture.py b/posthog/api/test/test_capture.py index 555f8d5361b2a..2a80186082dea 100644 --- a/posthog/api/test/test_capture.py +++ b/posthog/api/test/test_capture.py @@ -232,6 +232,7 @@ def _send_august_2023_version_session_recording_event( distinct_id="ghi789", timestamp=1658516991883, content_type: str | None = None, + query_params: str = "", ) -> HttpResponse: if event_data is None: # event_data is an array of RRWeb events @@ -264,7 +265,7 @@ def _send_august_2023_version_session_recording_event( post_data = {"api_key": self.team.api_token, "data": json.dumps([event for _ in range(number_of_events)])} return self.client.post( - "/s/", + "/s/" + "?" + query_params if query_params else "/s/", data=post_data, content_type=content_type or MULTIPART_CONTENT, ) @@ -1665,7 +1666,30 @@ def test_recording_ingestion_can_write_headers_with_the_message(self, kafka_prod ): self._send_august_2023_version_session_recording_event() - assert kafka_produce.mock_calls[0].kwargs["headers"] == [("token", "token123")] + assert kafka_produce.mock_calls[0].kwargs["headers"] == [ + ("token", "token123"), + ( + # without setting a version in the URL the default is unknown + "lib_version", + "unknown", + ), + ] + + @patch("posthog.kafka_client.client._KafkaProducer.produce") + def test_recording_ingestion_can_read_version_from_request(self, kafka_produce: MagicMock) -> None: + with self.settings( + SESSION_RECORDING_KAFKA_MAX_REQUEST_SIZE_BYTES=20480, + ): + self._send_august_2023_version_session_recording_event(query_params="ver=1.123.4") + + assert kafka_produce.mock_calls[0].kwargs["headers"] == [ + ("token", "token123"), + ( + # without setting a version in the URL the default is unknown + "lib_version", + "1.123.4", + ), + ] @patch("posthog.kafka_client.client.SessionRecordingKafkaProducer") def test_create_session_recording_kafka_with_expected_hosts( diff --git a/posthog/api/test/test_personal_api_keys.py b/posthog/api/test/test_personal_api_keys.py index afd7b924605a0..1fe9c3af847e9 100644 --- a/posthog/api/test/test_personal_api_keys.py +++ b/posthog/api/test/test_personal_api_keys.py @@ -3,6 +3,7 @@ from rest_framework import status from posthog.jwt import PosthogJwtAudience, encode_jwt +from posthog.models.insight import Insight from posthog.models.organization import Organization from posthog.models.personal_api_key import PersonalAPIKey, hash_key_value from posthog.models.team.team import Team @@ -422,6 +423,23 @@ def test_allows_overriding_write_scopes(self): ) assert response.status_code == status.HTTP_200_OK + def test_works_with_routes_missing_action(self): + insight = Insight.objects.create(team=self.team, name="XYZ", created_by=self.user) + + self.key.scopes = ["sharing_configuration:read"] + self.key.save() + response = self.client.patch( + f"/api/projects/{self.team.id}/insights/{insight.id}/sharing?personal_api_key={self.value}" + ) + assert response.status_code == status.HTTP_403_FORBIDDEN + + self.key.scopes = ["sharing_configuration:write"] + self.key.save() + response = self.client.patch( + f"/api/projects/{self.team.id}/insights/{insight.id}/sharing?personal_api_key={self.value}" + ) + assert response.status_code == status.HTTP_200_OK + class TestPersonalAPIKeysWithOrganizationScopeAPIAuthentication(PersonalAPIKeysBaseTest): def setUp(self): diff --git a/posthog/batch_exports/models.py b/posthog/batch_exports/models.py index 35fa6e8ba4754..70b85c4d35bde 100644 --- a/posthog/batch_exports/models.py +++ b/posthog/batch_exports/models.py @@ -306,5 +306,5 @@ class Status(models.TextChoices): def workflow_id(self) -> str: """Return the Workflow id that corresponds to this BatchExportBackfill model.""" start_at = self.start_at.strftime("%Y-%m-%dT%H:%M:%S") - end_at = self.end_at.strftime("%Y-%m-%dT%H:%M:%S") + end_at = self.end_at and self.end_at.strftime("%Y-%m-%dT%H:%M:%S") return f"{self.batch_export.id}-Backfill-{start_at}-{end_at}" diff --git a/posthog/hogql_queries/actors_query_runner.py b/posthog/hogql_queries/actors_query_runner.py index 1e714a3a2c0a0..93e9f40c70739 100644 --- a/posthog/hogql_queries/actors_query_runner.py +++ b/posthog/hogql_queries/actors_query_runner.py @@ -42,7 +42,7 @@ def determine_strategy(self) -> ActorStrategy: def get_recordings(self, event_results, recordings_lookup) -> Generator[dict, None, None]: return ( {"session_id": session_id, "events": recordings_lookup[session_id]} - for session_id in (event[2] for event in event_results) + for session_id in set(event[2] for event in event_results) if session_id in recordings_lookup ) @@ -66,7 +66,7 @@ def enrich_with_actors( yield new_row def prepare_recordings(self, column_name, input_columns): - if column_name != "person" or "matched_recordings" not in input_columns: + if (column_name != "person" and column_name != "actor") or "matched_recordings" not in input_columns: return None, None column_index_events = input_columns.index("matched_recordings") diff --git a/posthog/management/commands/backfill_distinct_id_overrides.py b/posthog/management/commands/backfill_distinct_id_overrides.py index 35133ef4addbf..507e744a93d0e 100644 --- a/posthog/management/commands/backfill_distinct_id_overrides.py +++ b/posthog/management/commands/backfill_distinct_id_overrides.py @@ -2,6 +2,7 @@ import logging from dataclasses import dataclass +from typing import Sequence import structlog from django.core.management.base import BaseCommand, CommandError @@ -14,10 +15,12 @@ @dataclass -class BackfillQuery: +class Backfill: team_id: int def execute(self, dry_run: bool = False) -> None: + logger.info("Starting %r...", self) + query = """ SELECT team_id, @@ -51,20 +54,54 @@ def execute(self, dry_run: bool = False) -> None: parameters, ) + # XXX: The RETURNING set isn't really useful here, but this QuerySet + # needs to be iterated over to force execution, so we might as well + # return something... + updated_teams = list( + Team.objects.raw( + """ + UPDATE posthog_team + SET extra_settings = COALESCE(extra_settings, '{}'::jsonb) || '{"distinct_id_overrides_backfilled": true}'::jsonb + WHERE id = %s + RETURNING * + """, + [self.team_id], + ) + ) + assert not len(updated_teams) > 1 + + logger.info("Completed %r!", self) + class Command(BaseCommand): help = "Backfill person_distinct_id_overrides records." def add_arguments(self, parser): - parser.add_argument("--team-id", required=True, type=int, help="team to backfill for") + parser.add_argument( + "--team-id", + required=False, + type=int, + dest="team_id_list", + action="append", + help="team(s) to backfill (defaults to all un-backfilled teams)", + ) parser.add_argument( "--live-run", action="store_true", help="actually execute INSERT queries (default is dry-run)" ) - def handle(self, *, live_run: bool, team_id: int, **options): + def handle(self, *, live_run: bool, team_id_list: Sequence[int] | None, **options): logger.setLevel(logging.INFO) - if not Team.objects.filter(id=team_id).exists(): - raise CommandError(f"Team with id={team_id!r} does not exist") + if team_id_list is not None: + team_ids = set(team_id_list) + existing_team_ids = set(Team.objects.filter(id__in=team_ids).values_list("id", flat=True)) + if existing_team_ids != team_ids: + raise CommandError(f"Teams with ids {team_ids - existing_team_ids!r} do not exist") + else: + team_ids = set( + Team.objects.exclude(extra_settings__distinct_id_overrides_backfilled=True).values_list("id", flat=True) + ) - BackfillQuery(team_id).execute(dry_run=not live_run) + logger.info("Starting backfill for %s teams...", len(team_ids)) + for team_id in team_ids: + Backfill(team_id).execute(dry_run=not live_run) diff --git a/posthog/management/commands/test/test_backfill_distinct_id_overrides.py b/posthog/management/commands/test/test_backfill_distinct_id_overrides.py index 6a161d6205d18..55f7c76116936 100644 --- a/posthog/management/commands/test/test_backfill_distinct_id_overrides.py +++ b/posthog/management/commands/test/test_backfill_distinct_id_overrides.py @@ -2,7 +2,7 @@ import uuid from posthog.clickhouse.client.execute import sync_execute -from posthog.management.commands.backfill_distinct_id_overrides import BackfillQuery +from posthog.management.commands.backfill_distinct_id_overrides import Backfill from posthog.test.base import BaseTest, ClickhouseTestMixin @@ -32,7 +32,7 @@ def __run_test_backfill(self, dry_run: bool) -> None: {"team_id": self.team.id}, ) == [(0,)] - BackfillQuery(self.team.id).execute(dry_run=dry_run) + Backfill(self.team.id).execute(dry_run=dry_run) read_columns = ["team_id", "distinct_id", "person_id", "version"] distinct_id_override_rows = sync_execute( diff --git a/posthog/permissions.py b/posthog/permissions.py index 4d42f165020bf..0d61b015d7f84 100644 --- a/posthog/permissions.py +++ b/posthog/permissions.py @@ -268,11 +268,19 @@ class APIScopePermission(BasePermission): """ - write_actions: list[str] = ["create", "update", "partial_update", "destroy"] + write_actions: list[str] = ["create", "update", "partial_update", "patch", "destroy"] read_actions: list[str] = ["list", "retrieve"] scope_object_read_actions: list[str] = [] scope_object_write_actions: list[str] = [] + def _get_action(self, request, view) -> str: + # TRICKY: DRF doesn't have an action for non-detail level "patch" calls which we use sometimes + + if not view.action: + if request.method == "PATCH" and not view.detail: + return "patch" + return view.action + def has_permission(self, request, view) -> bool: # NOTE: We do this first to error out quickly if the view is missing the required attribute # Helps devs remember to add it. @@ -341,12 +349,13 @@ def get_required_scopes(self, request, view) -> list[str]: if scope_object == "INTERNAL": raise PermissionDenied(f"This action does not support Personal API Key access") + action = self._get_action(request, view) read_actions = getattr(view, "scope_object_read_actions", self.read_actions) write_actions = getattr(view, "scope_object_write_actions", self.write_actions) - if view.action in write_actions: + if action in write_actions: return [f"{scope_object}:write"] - elif view.action in read_actions or request.method == "OPTIONS": + elif action in read_actions or request.method == "OPTIONS": return [f"{scope_object}:read"] # If we get here this typically means an action was called without a required scope diff --git a/posthog/temporal/data_imports/__init__.py b/posthog/temporal/data_imports/__init__.py index c6a142c712d39..e4d5887f22d15 100644 --- a/posthog/temporal/data_imports/__init__.py +++ b/posthog/temporal/data_imports/__init__.py @@ -1,10 +1,10 @@ from posthog.temporal.data_imports.external_data_job import ( ExternalDataJobWorkflow, create_external_data_job_model, - update_external_data_job_model, + create_source_templates, run_external_data_job, + update_external_data_job_model, validate_schema_activity, - create_source_templates, ) WORKFLOWS = [ExternalDataJobWorkflow] 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"