def destroy(self, request: request.Request, pk=None, **kwargs) -> Response: # type: ignore if not can_configure_plugins_via_api(self.team.organization_id): return Response(status=404) plugin_config = PluginConfig.objects.get(team_id=self.team_id, pk=pk) plugin_config.enabled = False plugin_config.save() return Response(status=204)
def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> PluginConfig: if not can_configure_plugins_via_api(): raise ValidationError("Plugin configuration via the web is disabled!") request = self.context["request"] validated_data["team"] = request.user.team plugin_config = super().create(validated_data) reload_plugins_on_workers() return plugin_config
def get_queryset(self): queryset = super().get_queryset() if self.action == "get" or self.action == "list": if can_install_plugins_via_api(self.organization) or can_configure_plugins_via_api(self.organization): return queryset else: if can_install_plugins_via_api(self.organization): return queryset return queryset.none()
def destroy(self, request: request.Request, pk=None) -> Response: # type: ignore if not can_configure_plugins_via_api(): return Response(status=404) plugin_config = PluginConfig.objects.get(team=request.user.team, pk=pk) plugin_config.enabled = False plugin_config.save() return Response(status=204)
def create(self, validated_data: Dict, *args: Any, **kwargs: Any) -> PluginConfig: if not can_configure_plugins_via_api(Team.objects.get(id=self.context["team_id"]).organization_id): raise ValidationError("Plugin configuration via the web is disabled!") request = self.context["request"] validated_data["team"] = Team.objects.get(id=self.context["team_id"]) self._fix_formdata_config_json(validated_data) plugin_config = super().create(validated_data) self._update_plugin_attachments(plugin_config) reload_plugins_on_workers() return plugin_config
def get_queryset(self): queryset = super().get_queryset() if self.action == "get" or self.action == "list": # type: ignore if can_install_plugins_via_api() or can_configure_plugins_via_api(): return queryset else: if can_install_plugins_via_api(): # block update/delete for plugins that come from posthog.json return queryset.filter(from_json=False) return queryset.none()
def global_plugins(self, request: request.Request, **kwargs): if not can_configure_plugins_via_api(self.team.organization_id): return Response([]) response = [] plugin_configs = PluginConfig.objects.filter(team_id=None, enabled=True) # type: ignore for plugin_config in plugin_configs: plugin = PluginConfigSerializer(plugin_config).data plugin["config"] = None plugin["error"] = None response.append(plugin) return Response(response)
def rearrange(self, request: request.Request, **kwargs): if not can_configure_plugins_via_api(self.team.organization_id): raise ValidationError( "Plugin configuration via the web is disabled!") orders = request.data.get("orders", {}) did_save = False plugin_configs = PluginConfig.objects.filter(team_id=self.team.pk, enabled=True) plugin_configs_dict = {p.plugin_id: p for p in plugin_configs} for plugin_id, order in orders.items(): plugin_config = plugin_configs_dict.get(int(plugin_id), None) if plugin_config and plugin_config.order != order: plugin_config.order = order plugin_config.save() did_save = True if did_save: reload_plugins_on_workers() return Response(PluginConfigSerializer(plugin_configs, many=True).data)
def get_queryset(self): if not can_configure_plugins_via_api(self.team.organization_id): return self.queryset.none() return super().get_queryset()
def user(request): organization: Optional[Organization] = request.user.organization organizations = list( request.user.organizations.order_by("-created_at").values( "name", "id")) team: Optional[Team] = request.user.team teams = list( request.user.teams.order_by("-created_at").values("name", "id")) user = cast(User, request.user) if request.method == "PATCH": data = json.loads(request.body) if team is not None and "team" in data: team.app_urls = data["team"].get("app_urls", team.app_urls) team.opt_out_capture = data["team"].get("opt_out_capture", team.opt_out_capture) team.slack_incoming_webhook = data["team"].get( "slack_incoming_webhook", team.slack_incoming_webhook) team.anonymize_ips = data["team"].get("anonymize_ips", team.anonymize_ips) team.session_recording_opt_in = data["team"].get( "session_recording_opt_in", team.session_recording_opt_in) team.session_recording_retention_period_days = data["team"].get( "session_recording_retention_period_days", team.session_recording_retention_period_days) if data["team"].get("plugins_opt_in") is not None: reload_plugins_on_workers() team.plugins_opt_in = data["team"].get("plugins_opt_in", team.plugins_opt_in) team.completed_snippet_onboarding = data["team"].get( "completed_snippet_onboarding", team.completed_snippet_onboarding, ) team.save() if "user" in data: try: user.current_organization = user.organizations.get( id=data["user"]["current_organization_id"]) assert user.organization is not None, "Organization should have been just set" user.current_team = user.organization.teams.first() except (KeyError, ValueError): pass except ObjectDoesNotExist: return JsonResponse( {"detail": "Organization not found for user."}, status=404) except KeyError: pass except ObjectDoesNotExist: return JsonResponse( {"detail": "Organization not found for user."}, status=404) if user.organization is not None: try: user.current_team = user.organization.teams.get( id=int(data["user"]["current_team_id"])) except (KeyError, TypeError): pass except ValueError: return JsonResponse( {"detail": "Team ID must be an integer."}, status=400) except ObjectDoesNotExist: return JsonResponse( { "detail": "Team not found for user's current organization." }, status=404) user.email_opt_in = data["user"].get("email_opt_in", user.email_opt_in) user.anonymize_data = data["user"].get("anonymize_data", user.anonymize_data) user.toolbar_mode = data["user"].get("toolbar_mode", user.toolbar_mode) user.save() user_identify.identify_task.delay(user_id=user.id) return JsonResponse({ "id": user.pk, "distinct_id": user.distinct_id, "name": user.first_name, "email": user.email, "email_opt_in": user.email_opt_in, "anonymize_data": user.anonymize_data, "toolbar_mode": user.toolbar_mode, "organization": None if organization is None else { "id": organization.id, "name": organization.name, "billing_plan": organization.billing_plan, "available_features": organization.available_features, "created_at": organization.created_at, "updated_at": organization.updated_at, "teams": [{ "id": team.id, "name": team.name } for team in organization.teams.all().only("id", "name")], }, "organizations": organizations, "team": None if team is None else { "id": team.id, "name": team.name, "app_urls": team.app_urls, "api_token": team.api_token, "opt_out_capture": team.opt_out_capture, "anonymize_ips": team.anonymize_ips, "slack_incoming_webhook": team.slack_incoming_webhook, "event_names": team.event_names, "event_names_with_usage": team.event_names_with_usage or [{ "event": event, "volume": None, "usage_count": None } for event in team.event_names], "event_properties": team.event_properties, "event_properties_numerical": team.event_properties_numerical, "event_properties_with_usage": team.event_properties_with_usage or [{ "key": key, "volume": None, "usage_count": None } for key in team.event_properties], "completed_snippet_onboarding": team.completed_snippet_onboarding, "session_recording_opt_in": team.session_recording_opt_in, "session_recording_retention_period_days": team.session_recording_retention_period_days, "plugins_opt_in": team.plugins_opt_in, "ingested_event": team.ingested_event, }, "teams": teams, "has_password": user.has_usable_password(), "opt_out_capture": os.environ.get("OPT_OUT_CAPTURE"), "posthog_version": VERSION, "is_multi_tenancy": getattr(settings, "MULTI_TENANCY", False), "ee_available": settings.EE_AVAILABLE, "ee_enabled": is_ee_enabled(), "email_service_available": is_email_available(with_absolute_urls=True), "is_debug": getattr(settings, "DEBUG", False), "is_staff": user.is_staff, "is_impersonated": is_impersonated_session(request), "plugin_access": { "install": can_install_plugins_via_api(user.organization), "configure": can_configure_plugins_via_api(user.organization), }, })
def system_status(request): team = request.user.team is_multitenancy: bool = getattr(settings, "MULTI_TENANCY", False) if is_multitenancy and not request.user.is_staff: raise AuthenticationFailed(detail="You're not authorized.") from .models import Element, Event, SessionRecordingEvent redis_alive = is_redis_alive() postgres_alive = is_postgres_alive() metrics: List[Dict[str, Union[str, bool, int, float]]] = [] metrics.append({ "key": "analytics_database", "metric": "Analytics database in use", "value": "ClickHouse" if is_ee_enabled() else "Postgres", }) metrics.append({ "key": "ingestion_server", "metric": "Event ingestion via", "value": "Plugin Server" if settings.PLUGIN_SERVER_INGESTION else "Django", }) metrics.append({ "key": "db_alive", "metric": "Postgres database alive", "value": postgres_alive }) if postgres_alive: postgres_version = connection.cursor().connection.server_version metrics.append({ "key": "pg_version", "metric": "Postgres version", "value": f"{postgres_version // 10000}.{(postgres_version // 100) % 100}.{postgres_version % 100}", }) event_table_count = get_table_approx_count( Event._meta.db_table)[0]["approx_count"] event_table_size = get_table_size(Event._meta.db_table)[0]["size"] element_table_count = get_table_approx_count( Element._meta.db_table)[0]["approx_count"] element_table_size = get_table_size(Element._meta.db_table)[0]["size"] session_recording_event_table_count = get_table_approx_count( SessionRecordingEvent._meta.db_table)[0]["approx_count"] session_recording_event_table_size = get_table_size( SessionRecordingEvent._meta.db_table)[0]["size"] metrics.append({ "metric": "Postgres elements table size", "value": f"~{element_table_count} rows (~{element_table_size})" }) metrics.append({ "metric": "Postgres events table size", "value": f"~{event_table_count} rows (~{event_table_size})" }) metrics.append({ "metric": "Postgres session recording table size", "value": f"~{session_recording_event_table_count} rows (~{session_recording_event_table_size})", }) metrics.append({ "key": "redis_alive", "metric": "Redis alive", "value": redis_alive }) if redis_alive: import redis try: redis_info = get_redis_info() redis_queue_depth = get_redis_queue_depth() metrics.append({ "metric": "Redis version", "value": f"{redis_info.get('redis_version')}" }) metrics.append({ "metric": "Redis current queue depth", "value": f"{redis_queue_depth}" }) metrics.append({ "metric": "Redis connected client count", "value": f"{redis_info.get('connected_clients')}" }) metrics.append({ "metric": "Redis memory used", "value": f"{redis_info.get('used_memory_human', '?')}B" }) metrics.append({ "metric": "Redis memory peak", "value": f"{redis_info.get('used_memory_peak_human', '?')}B" }) metrics.append({ "metric": "Redis total memory available", "value": f"{redis_info.get('total_system_memory_human', '?')}B", }) except redis.exceptions.ConnectionError as e: metrics.append({ "metric": "Redis metrics", "value": f"Redis connected but then failed to return metrics: {e}" }) metrics.append({ "key": "plugin_sever_alive", "metric": "Plugin server alive", "value": is_plugin_server_alive() }) metrics.append({ "key": "plugin_sever_version", "metric": "Plugin server version", "value": get_plugin_server_version() or "unknown", }) metrics.append({ "key": "plugins_install", "metric": "Plugins can be installed", "value": can_install_plugins_via_api(team.organization), }) metrics.append({ "key": "plugins_configure", "metric": "Plugins can be configured", "value": can_configure_plugins_via_api(team.organization), }) return JsonResponse({"results": metrics})
def get_queryset(self): queryset = super().get_queryset() if can_configure_plugins_via_api(): return queryset.filter(team_id=self.request.user.team.pk) return queryset.none()
def user(request): organization = request.user.organization organizations = list( request.user.organizations.order_by("-created_at").values( "name", "id")) team = request.user.team teams = list( request.user.teams.order_by("-created_at").values("name", "id")) user = cast(User, request.user) if request.method == "PATCH": data = json.loads(request.body) if "team" in data: team.app_urls = data["team"].get("app_urls", team.app_urls) team.opt_out_capture = data["team"].get("opt_out_capture", team.opt_out_capture) team.slack_incoming_webhook = data["team"].get( "slack_incoming_webhook", team.slack_incoming_webhook) team.anonymize_ips = data["team"].get("anonymize_ips", team.anonymize_ips) team.session_recording_opt_in = data["team"].get( "session_recording_opt_in", team.session_recording_opt_in) if data["team"].get("plugins_opt_in") is not None: reload_plugins_on_workers() team.plugins_opt_in = data["team"].get("plugins_opt_in", team.plugins_opt_in) team.completed_snippet_onboarding = data["team"].get( "completed_snippet_onboarding", team.completed_snippet_onboarding, ) team.save() if "user" in data: try: user.current_organization = user.organizations.get( id=data["user"]["current_organization_id"]) user.current_team = user.organization.teams.first() except KeyError: pass except ObjectDoesNotExist: return JsonResponse( {"detail": "Organization not found for user."}, status=404) except KeyError: pass except ObjectDoesNotExist: return JsonResponse( {"detail": "Organization not found for user."}, status=404) try: user.current_team = user.organization.teams.get( id=int(data["user"]["current_team_id"])) except (KeyError, TypeError): pass except ValueError: return JsonResponse({"detail": "Team ID must be an integer."}, status=400) except ObjectDoesNotExist: return JsonResponse( { "detail": "Team not found for user's current organization." }, status=404) user.email_opt_in = data["user"].get("email_opt_in", user.email_opt_in) user.anonymize_data = data["user"].get("anonymize_data", user.anonymize_data) user.toolbar_mode = data["user"].get("toolbar_mode", user.toolbar_mode) posthoganalytics.identify( user.distinct_id, { "email_opt_in": user.email_opt_in, "anonymize_data": user.anonymize_data, "email": user.email if not user.anonymize_data else None, "is_signed_up": True, "toolbar_mode": user.toolbar_mode, "billing_plan": user.organization.billing_plan, "is_team_unique_user": (team.users.count() == 1), "team_setup_complete": (team.completed_snippet_onboarding and team.ingested_event), }, ) user.save() return JsonResponse({ "id": user.pk, "distinct_id": user.distinct_id, "name": user.first_name, "email": user.email, "email_opt_in": user.email_opt_in, "anonymize_data": user.anonymize_data, "toolbar_mode": user.toolbar_mode, "organization": { "id": organization.id, "name": organization.name, "billing_plan": organization.billing_plan, "available_features": organization.available_features, "created_at": organization.created_at, "updated_at": organization.updated_at, "teams": [{ "id": team.id, "name": team.name } for team in organization.teams.all().only("id", "name")], }, "organizations": organizations, "team": team and { "id": team.id, "name": team.name, "app_urls": team.app_urls, "api_token": team.api_token, "opt_out_capture": team.opt_out_capture, "anonymize_ips": team.anonymize_ips, "slack_incoming_webhook": team.slack_incoming_webhook, "event_names": team.event_names, "event_properties": team.event_properties, "event_properties_numerical": team.event_properties_numerical, "completed_snippet_onboarding": team.completed_snippet_onboarding, "session_recording_opt_in": team.session_recording_opt_in, "plugins_opt_in": team.plugins_opt_in, "ingested_event": team.ingested_event, }, "teams": teams, "has_password": user.has_usable_password(), "opt_out_capture": os.environ.get("OPT_OUT_CAPTURE"), "posthog_version": VERSION, "is_multi_tenancy": getattr(settings, "MULTI_TENANCY", False), "ee_available": user.ee_available, "plugin_access": { "install": can_install_plugins_via_api(), "configure": can_configure_plugins_via_api() }, })
def get_queryset(self): if not can_configure_plugins_via_api(): return self.queryset.none() return super().get_queryset()