Skip to content

Commit

Permalink
Merge branch 'master' into ch-24.3
Browse files Browse the repository at this point in the history
  • Loading branch information
fuziontech authored Oct 11, 2024
2 parents 859e43b + 4ef6fe5 commit 5f86737
Show file tree
Hide file tree
Showing 149 changed files with 3,221 additions and 471 deletions.
4 changes: 4 additions & 0 deletions bin/deploy-hobby
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export SENTRY_DSN="${SENTRY_DSN:-'https://[email protected]/1'}"
POSTHOG_SECRET=$(head -c 28 /dev/urandom | sha224sum -b | head -c 56)
export POSTHOG_SECRET

ENCRYPTION_KEY=$(openssl rand -hex 16)
export ENCRYPTION_SALT_KEYS

# Talk to the user
echo "Welcome to the single instance PostHog installer 🦔"
echo ""
Expand Down Expand Up @@ -128,6 +131,7 @@ EOF
# Write .env file
envsubst > .env <<EOF
POSTHOG_SECRET=$POSTHOG_SECRET
ENCRYPTION_SALT_KEYS=$ENCRYPTION_SALT_KEYS
SENTRY_DSN=$SENTRY_DSN
DOMAIN=$DOMAIN
EOF
Expand Down
19 changes: 16 additions & 3 deletions bin/temporal-django-worker
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@

set -e

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
cleanup() {
echo "Stopping worker..."
if kill -0 "$worker_pid" >/dev/null 2>&1; then
kill "$worker_pid"
else
echo "Worker process is not running."
fi
}

python3 manage.py start_temporal_worker "$@"
trap cleanup SIGINT SIGTERM EXIT

wait
python3 manage.py start_temporal_worker "$@" &

worker_pid=$!

wait $worker_pid

cleanup
24 changes: 23 additions & 1 deletion bin/upgrade-hobby
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
set -e

echo "Upgrading PostHog. This will cause a few minutes of downtime."
read -r -p "Do you want to upgarde PostHog? [y/N] " response
read -r -p "Do you want to upgrade PostHog? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]
then
echo "OK!"
Expand Down Expand Up @@ -56,6 +56,28 @@ else
fi

[[ -f ".env" ]] && export $(cat .env | xargs) || ( echo "No .env file found. Please create it with POSTHOG_SECRET and DOMAIN set." && exit 1)

# we introduced ENCRYPTION_SALT_KEYS and so if there isn't one, need to add it
# check for it in the .env file
if ! grep -q "ENCRYPTION_SALT_KEYS" .env; then
ENCRYPTION_KEY=$(openssl rand -hex 16)
echo "ENCRYPTION_SALT_KEYS=$ENCRYPTION_KEY" >> .env
echo "Added missing ENCRYPTION_SALT_KEYS to .env file"
source .env
else
# Read the existing key
EXISTING_KEY=$(grep "ENCRYPTION_SALT_KEYS" .env | cut -d '=' -f2)

# Check if the existing key is in the correct format (32 bytes base64url)
if [[ ! $EXISTING_KEY =~ ^[A-Za-z0-9_-]{32}$ ]]; then
echo "ENCRYPTION_SALT_KEYS is not in the correct fernet format and will not work"
echo "🛑 Stop this script and do not proceed"
echo "remove ENCRYPTION_SALT_KEYS from .env and try again"
exit 1
fi
fi


export POSTHOG_APP_TAG="${POSTHOG_APP_TAG:-latest-release}"

cd posthog
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.hobby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ services:
OBJECT_STORAGE_SECRET_ACCESS_KEY: 'object_storage_root_password'
OBJECT_STORAGE_ENDPOINT: http://objectstorage:19000
OBJECT_STORAGE_ENABLED: true
ENCRYPTION_SALT_KEYS: $ENCRYPTION_SALT_KEYS
image: $REGISTRY_URL:$POSTHOG_APP_TAG
web:
extends:
Expand All @@ -100,6 +101,7 @@ services:
OBJECT_STORAGE_SECRET_ACCESS_KEY: 'object_storage_root_password'
OBJECT_STORAGE_ENDPOINT: http://objectstorage:19000
OBJECT_STORAGE_ENABLED: true
ENCRYPTION_SALT_KEYS: $ENCRYPTION_SALT_KEYS
depends_on:
- db
- redis
Expand All @@ -122,6 +124,7 @@ services:
OBJECT_STORAGE_ENABLED: true
CDP_REDIS_HOST: redis7
CDP_REDIS_PORT: 6379
ENCRYPTION_SALT_KEYS: $ENCRYPTION_SALT_KEYS
depends_on:
- db
- redis
Expand Down
9 changes: 7 additions & 2 deletions docker/clickhouse/docker-entrypoint-initdb.d/init-db.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ set -e

apt-get update
apt-get -y install python3.9
ln -s /usr/bin/python3.9 /usr/bin/python3
cp -r /idl/* /var/lib/clickhouse/format_schemas/

# Check if /usr/bin/python3 already exists and if it points to python3.9
if [[ $(readlink /usr/bin/python3) != "/usr/bin/python3.9" ]]; then
ln -sf /usr/bin/python3.9 /usr/bin/python3
fi

cp -r /idl/* /var/lib/clickhouse/format_schemas/
30 changes: 22 additions & 8 deletions ee/api/billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
from rest_framework.request import Request
from rest_framework.response import Response
from django.contrib.auth.models import AbstractUser

from ee.billing.billing_manager import BillingManager, build_billing_token
from ee.models import License
Expand Down Expand Up @@ -40,6 +41,13 @@ class BillingViewset(TeamAndOrgViewSetMixin, viewsets.GenericViewSet):

scope_object = "INTERNAL"

def get_billing_manager(self) -> BillingManager:
license = get_cached_instance_license()
user = (
self.request.user if isinstance(self.request.user, AbstractUser) and self.request.user.distinct_id else None
)
return BillingManager(license, user)

def list(self, request: Request, *args: Any, **kwargs: Any) -> Response:
license = get_cached_instance_license()
if license and not license.is_v2_license:
Expand All @@ -53,7 +61,8 @@ def list(self, request: Request, *args: Any, **kwargs: Any) -> Response:
raise NotFound("Billing V1 is active for this organization")

plan_keys = request.query_params.get("plan_keys", None)
response = BillingManager(license).get_billing(org, plan_keys)
billing_manager = self.get_billing_manager()
response = billing_manager.get_billing(org, plan_keys)

return Response(response)

Expand All @@ -68,7 +77,8 @@ def patch(self, request: Request, *args: Any, **kwargs: Any) -> Response:
if license and org: # for mypy
custom_limits_usd = request.data.get("custom_limits_usd")
if custom_limits_usd:
BillingManager(license).update_billing(org, {"custom_limits_usd": custom_limits_usd})
billing_manager = self.get_billing_manager()
billing_manager.update_billing(org, {"custom_limits_usd": custom_limits_usd})

if distinct_id:
posthoganalytics.capture(
Expand Down Expand Up @@ -153,7 +163,6 @@ class DeactivateSerializer(serializers.Serializer):

@action(methods=["GET"], detail=False)
def deactivate(self, request: Request, *args: Any, **kwargs: Any) -> HttpResponse:
license = get_cached_instance_license()
organization = self._get_org_required()

serializer = self.DeactivateSerializer(data=request.GET)
Expand All @@ -162,7 +171,8 @@ def deactivate(self, request: Request, *args: Any, **kwargs: Any) -> HttpRespons
products = serializer.validated_data.get("products")

try:
BillingManager(license).deactivate_products(organization, products)
billing_manager = self.get_billing_manager()
billing_manager.deactivate_products(organization, products)
except Exception as e:
if len(e.args) > 2:
detail_object = e.args[2]
Expand Down Expand Up @@ -191,7 +201,8 @@ def portal(self, request: Request, *args: Any, **kwargs: Any) -> HttpResponse:

organization = self._get_org_required()

res = BillingManager(license)._get_stripe_portal_url(organization)
billing_manager = self.get_billing_manager()
res = billing_manager._get_stripe_portal_url(organization)
return redirect(res)

@action(methods=["GET"], detail=False)
Expand All @@ -208,7 +219,8 @@ def get_invoices(self, request: Request, *args: Any, **kwargs: Any) -> HttpRespo
invoice_status = request.GET.get("status")

try:
res = BillingManager(license).get_invoices(organization, status=invoice_status)
billing_manager = self.get_billing_manager()
res = billing_manager.get_invoices(organization, status=invoice_status)
except Exception as e:
if len(e.args) > 2:
detail_object = e.args[2]
Expand Down Expand Up @@ -244,7 +256,8 @@ def credits_overview(self, request: Request, *args: Any, **kwargs: Any) -> HttpR

organization = self._get_org_required()

res = BillingManager(license).credits_overview(organization)
billing_manager = self.get_billing_manager()
res = billing_manager.credits_overview(organization)
return Response(res, status=status.HTTP_200_OK)

@action(methods=["POST"], detail=False, url_path="credits/purchase")
Expand All @@ -258,7 +271,8 @@ def purchase_credits(self, request: Request, *args: Any, **kwargs: Any) -> HttpR

organization = self._get_org_required()

res = BillingManager(license).purchase_credits(organization, request.data)
billing_manager = self.get_billing_manager()
res = billing_manager.purchase_credits(organization, request.data)
return Response(res, status=status.HTTP_200_OK)

@action(methods=["PATCH"], detail=False)
Expand Down
1 change: 1 addition & 0 deletions ee/api/test/test_billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma

assert decoded_token == {
"aud": "posthog:license-key",
"distinct_id": str(self.user.distinct_id),
"exp": 1640996100,
"id": self.license.key.split("::")[0],
"organization_id": str(self.organization.id),
Expand Down
28 changes: 18 additions & 10 deletions ee/billing/billing_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from posthog.cloud_utils import get_cached_instance_license
from posthog.models import Organization
from posthog.models.organization import OrganizationMembership, OrganizationUsageInfo
from posthog.models.user import User

logger = structlog.get_logger(__name__)

Expand All @@ -28,21 +29,26 @@ class BillingAPIErrorCodes(Enum):
OPEN_INVOICES_ERROR = "open_invoices_error"


def build_billing_token(license: License, organization: Organization):
def build_billing_token(license: License, organization: Organization, user: Optional[User] = None):
if not organization or not license:
raise NotAuthenticated()

license_id = license.key.split("::")[0]
license_secret = license.key.split("::")[1]

payload = {
"exp": datetime.now(tz=timezone.utc) + timedelta(minutes=15),
"id": license_id,
"organization_id": str(organization.id),
"organization_name": organization.name,
"aud": "posthog:license-key",
}

if user:
payload["distinct_id"] = str(user.distinct_id)

encoded_jwt = jwt.encode(
{
"exp": datetime.now(tz=timezone.utc) + timedelta(minutes=15),
"id": license_id,
"organization_id": str(organization.id),
"organization_name": organization.name,
"aud": "posthog:license-key",
},
payload,
license_secret,
algorithm="HS256",
)
Expand All @@ -62,9 +68,11 @@ def handle_billing_service_error(res: requests.Response, valid_codes=(200, 404,

class BillingManager:
license: Optional[License]
user: Optional[User]

def __init__(self, license):
def __init__(self, license, user: Optional[User] = None):
self.license = license or get_cached_instance_license()
self.user = user

def get_billing(self, organization: Optional[Organization], plan_keys: Optional[str]) -> dict[str, Any]:
if organization and self.license and self.license.is_v2_license:
Expand Down Expand Up @@ -331,7 +339,7 @@ def update_org_details(self, organization: Organization, billing_status: Billing
def get_auth_headers(self, organization: Organization):
if not self.license: # mypy
raise Exception("No license found")
billing_service_token = build_billing_token(self.license, organization)
billing_service_token = build_billing_token(self.license, organization, self.user)
return {"Authorization": f"Bearer {billing_service_token}"}

def get_invoices(self, organization: Organization, status: Optional[str]):
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-persons-groups--cohorts--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 7 additions & 1 deletion frontend/src/layout/navigation-3000/sidepanel/SidePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './SidePanel.scss'

import { IconEllipsis, IconFeatures, IconGear, IconInfo, IconNotebook, IconSupport } from '@posthog/icons'
import { IconEllipsis, IconFeatures, IconFlag, IconGear, IconInfo, IconNotebook, IconSupport } from '@posthog/icons'
import { LemonButton, LemonMenu, LemonMenuItems, LemonModal } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
Expand All @@ -20,6 +20,7 @@ import { SidePanelActivation, SidePanelActivationIcon } from './panels/activatio
import { SidePanelActivity, SidePanelActivityIcon } from './panels/activity/SidePanelActivity'
import { SidePanelDiscussion, SidePanelDiscussionIcon } from './panels/discussion/SidePanelDiscussion'
import { SidePanelDocs } from './panels/SidePanelDocs'
import { SidePanelExperimentFeatureFlag } from './panels/SidePanelExperimentFeatureFlag'
import { SidePanelFeaturePreviews } from './panels/SidePanelFeaturePreviews'
import { SidePanelSettings } from './panels/SidePanelSettings'
import { SidePanelStatus, SidePanelStatusIcon } from './panels/SidePanelStatus'
Expand Down Expand Up @@ -87,6 +88,11 @@ export const SIDE_PANEL_TABS: Record<
Content: SidePanelStatus,
noModalSupport: true,
},
[SidePanelTab.ExperimentFeatureFlag]: {
label: 'Release conditions',
Icon: IconFlag,
Content: SidePanelExperimentFeatureFlag,
},
}

const DEFAULT_WIDTH = 512
Expand Down
Loading

0 comments on commit 5f86737

Please sign in to comment.