Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ jobs:
docker image ls -a
# - name: Run e2e tests // TODO: Uncomment when we fix e2e tests
# env:
# LAUCHDARKLY_SDK_KEY: ${{ secrets.LAUCHDARKLY_SDK_KEY }}
# OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }}
# OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
# run: make cypress
Expand Down Expand Up @@ -242,9 +241,9 @@ jobs:
DEEPCHECKS_CI_TOKEN: ${{ secrets.DEEPCHECKS_CI_TOKEN }}
DEEPCHECKS_API_TOKEN: ${{ env.DEEPCHECKS_API_TOKEN }}
DEEPCHECKS_API_HOST: ${{ env.DEEPCHECKS_API_HOST }}
LAUCHDARKLY_SDK_KEY: ${{ secrets.LAUCHDARKLY_SDK_KEY }}
OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }}
OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
IS_ON_PREM: 'True'
run: |
make env-setup
make docs
Expand Down
3 changes: 3 additions & 0 deletions backend/deepchecks_monitoring/api/v1/global_api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ async def update_complete_details(
resources_provider=ResourcesProviderDep
):
"""Complete user details for final login."""

resources_provider: "ResourcesProvider"

if body.new_organization_name is not None and body.accept_invite is True:
raise BadRequest("Can't accept invitation and create new organization")

Expand Down
23 changes: 11 additions & 12 deletions backend/deepchecks_monitoring/api/v1/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,18 +177,17 @@ async def get_create_model(
else:
model_count = await session.scalar(sa.func.count(Model.id))
if model_count > 0:
if features_control.max_models != -1:
allowed_models = await features_control.get_allowed_models(session)
if allowed_models == 1:
raise PaymentRequired("Adding more than 1 model requires to set up a subscription. "
f"Set up through {resources_provider.settings.deployment_url}"
f"/workspace-settings")
if allowed_models < model_count:
raise PaymentRequired(f"Subscription currently configured for {allowed_models} models. "
f"Current model amount is {model_count}. "
"please update your subscription if you wish to add more models. "
f"Update through {resources_provider.settings.deployment_url}"
f"/workspace-settings")
allowed_models = await features_control.get_allowed_models(session)
if allowed_models == 1:
raise PaymentRequired("Adding more than 1 model requires to set up a subscription. "
f"Set up through {resources_provider.settings.deployment_url}"
f"/workspace-settings")
if allowed_models is not None and allowed_models < model_count:
raise PaymentRequired(f"Subscription currently configured for {allowed_models} models. "
f"Current model amount is {model_count}. "
"please update your subscription if you wish to add more models. "
f"Update through {resources_provider.settings.deployment_url}"
f"/workspace-settings")
data = model_schema.dict(exclude_none=True)
notes = [ModelNote(created_by=user.id, updated_by=user.id, **it) for it in data.pop("notes", [])]
model = Model(notes=notes, created_by=user.id, updated_by=user.id, **data)
Expand Down
1 change: 1 addition & 0 deletions backend/deepchecks_monitoring/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class Settings(
oauth_client_secret: str
mixpanel_id: str | None
enable_analytics: bool = True
parallel_check_executor_flag: bool = True

init_local_ray_instance: str | None = None
total_number_of_check_executor_actors: int = os.cpu_count() or 8
Expand Down
1 change: 0 additions & 1 deletion backend/deepchecks_monitoring/ee/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ class Settings(

enviroment: str = 'dev'
debug_mode: bool = False
lauchdarkly_sdk_key: str = ''
access_audit: bool = False
hotjar_sv: str = ''
hotjar_id: str = ''
Expand Down
102 changes: 6 additions & 96 deletions backend/deepchecks_monitoring/ee/features_control_cloud.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,16 @@
from ldclient import Context
from ldclient.client import LDClient
from pydantic import BaseModel
from sqlalchemy import select

from deepchecks_monitoring.features_control import FeaturesControl
from deepchecks_monitoring.public_models import Billing, User


class TierConfSchema(BaseModel):
"""Tier configuration which is loaded from launchdarkly."""

custom_checks: bool = False
data_retention_months: int = 3
max_models: int = 1
monthly_predictions_limit: int = 500_000
sso: bool = False
rows_per_minute: int = 500_000
update_roles: bool = False
model_assignment: bool = False


class CloudFeaturesControl(FeaturesControl):
"""Feature controls class for the cloud version."""

def __init__(self, user: User, ld_client: LDClient, settings):
def __init__(self, user: User, settings):
super().__init__(settings)
self.user = user
self.ld_client = ld_client
self._max_models = None
self._allowed_models = None
self._rows_per_minute = None
self._custom_checks_enabled = None
self._data_retention_months = None
self._monthly_predictions_limit = None
self._sso_enabled = None
self._signup_enabled = None
self._onboarding_enabled = None
self._update_roles = None
self._model_assignment = None

@property
def max_models(self) -> int:
if self._max_models is None:
self._load_tier()
return self._max_models

async def get_allowed_models(self, session) -> int:
if self._allowed_models is None:
Expand All @@ -57,85 +24,28 @@ async def get_allowed_models(self, session) -> int:

@property
def update_roles(self) -> bool:
if self._update_roles is None:
self._load_tier()
return self._update_roles
return True

@property
def model_assignment(self) -> bool:
if self._model_assignment is None:
self._load_tier()
return self._model_assignment
return True

@property
def signup_enabled(self) -> bool:
if self._signup_enabled is None:
self._load_tier()
return self._signup_enabled
return True

@property
def onboarding_enabled(self) -> bool:
if self._onboarding_enabled is None:
self._load_tier()
return self._onboarding_enabled
return True

@property
def slack_enabled(self) -> bool:
return True

@property
def rows_per_minute(self) -> int:
if self._rows_per_minute is None:
self._load_tier()
return self._rows_per_minute

@property
def custom_checks_enabled(self) -> bool:
if self._custom_checks_enabled is None:
self._load_tier()
return self._custom_checks_enabled

@property
def data_retention_months(self) -> int:
if self._data_retention_months is None:
self._load_tier()
return self._data_retention_months

@property
def monthly_predictions_limit(self) -> int:
if self._monthly_predictions_limit is None:
self._load_tier()
return self._monthly_predictions_limit

@property
def sso_enabled(self) -> bool:
if self._sso_enabled is None:
self._load_tier()
return self._sso_enabled
return 500_000

@property
def multi_tenant(self) -> bool:
return True

def _load_tier(self):
context = Context.builder(self.user.email).set("email", self.user.email)
if self.user.organization:
context.set("organization_id", self.user.organization.id)
context.set("tier", self.user.organization.tier)

ld_user = context.build()
tier_conf = self.ld_client.variation("paid-features", ld_user, default={})
if getattr(self.user, "email_verified", False):
self._signup_enabled = self.ld_client.variation("signUpEnabled", ld_user, default=True)
else:
self._signup_enabled = False
tier_conf = TierConfSchema(**tier_conf)
self._custom_checks_enabled = tier_conf.custom_checks
self._data_retention_months = tier_conf.data_retention_months
self._max_models = tier_conf.max_models
self._monthly_predictions_limit = tier_conf.monthly_predictions_limit
self._sso_enabled = tier_conf.sso
self._rows_per_minute = tier_conf.rows_per_minute
self._update_roles = tier_conf.update_roles
self._model_assignment = tier_conf.model_assignment
self._onboarding_enabled = self.ld_client.variation("onBoardingEnabled", ld_user, default=False)
24 changes: 2 additions & 22 deletions backend/deepchecks_monitoring/ee/features_control_on_prem.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@ class OnPremFeaturesControl(FeaturesControl):
TODO: implement license check :(
"""

@property
def max_models(self) -> int:
return 9999

async def get_allowed_models(self, session) -> int:
return 10
async def get_allowed_models(self, session) -> None:
return None

@property
def update_roles(self) -> bool:
Expand All @@ -37,22 +33,6 @@ def slack_enabled(self) -> bool:
def rows_per_minute(self) -> int:
return 500_000

@property
def custom_checks_enabled(self) -> bool:
return False

@property
def data_retention_months(self) -> int:
return 12

@property
def monthly_predictions_limit(self) -> int:
return 10_000_000

@property
def sso_enabled(self) -> bool:
return False

@property
def multi_tenant(self) -> bool:
return False
36 changes: 4 additions & 32 deletions backend/deepchecks_monitoring/ee/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
import logging
from typing import TYPE_CHECKING, cast

import ldclient
from ldclient import Context
from ldclient.client import LDClient
from ldclient.config import Config as LDConfig

from deepchecks_monitoring.ee.config import Settings, SlackSettings
from deepchecks_monitoring.ee.features_control_cloud import CloudFeaturesControl
from deepchecks_monitoring.ee.features_control_on_prem import OnPremFeaturesControl
Expand All @@ -38,10 +33,6 @@ class ResourcesProvider(OpenSourceResourcesProvider):

ALERT_NOTIFICATOR_TYPE = EEAlertNotificator

def __init__(self, settings: Settings):
super().__init__(settings)
self._lauchdarkly_client = None

@property
def slack_settings(self) -> SlackSettings:
"""Get the telemetry settings."""
Expand All @@ -60,36 +51,18 @@ def email_sender(self) -> EmailSender:
self._email_sender = EmailSender(self.email_settings)
return self._email_sender

@property
def lauchdarkly_client(self) -> LDClient:
"""Launchdarkly client."""
if self.settings.is_cloud is False:
raise Exception("Launchdarkly client is only available in cloud mode")
if self._lauchdarkly_client:
return self._lauchdarkly_client
ldclient.set_config(LDConfig(self.settings.lauchdarkly_sdk_key))
self._lauchdarkly_client = ldclient.get()
return self._lauchdarkly_client

def get_features_control(self, user: User) -> FeaturesControl:
"""Return features control."""
if self.settings.is_cloud:
return CloudFeaturesControl(user, self.lauchdarkly_client, self.settings)
# TODO add license check -
elif self.settings.is_on_prem:
if self.settings.is_on_prem:
return OnPremFeaturesControl(self.settings)
if self.settings.is_cloud:
return CloudFeaturesControl(user, self.settings)
return FeaturesControl(self.settings)

@property
def parallel_check_executors_pool(self) -> "ActorPool | None":
if self.settings.is_cloud is False:
parallel_check_executor_flag = True
else:
parallel_check_executor_flag = self.lauchdarkly_client.variation(
"parallelCheckExecutorEnabled",
context=Context.builder("parallelCheckExecutorEnabled").build(),
default=True
)
parallel_check_executor_flag = self.settings.parallel_check_executor_flag

logging.getLogger("server").info({
"mesage": f"'parallelCheckExecutorEnabled' is set to {parallel_check_executor_flag}"
Expand All @@ -101,7 +74,6 @@ def get_client_configuration(self) -> dict:
if self.settings.is_cloud:
settings = cast(Settings, self.settings)
return {
"lauchdarklySdkKey": settings.lauchdarkly_sdk_key,
"environment": settings.enviroment,
"mixpanel_id": settings.mixpanel_id,
"is_cloud": True,
Expand Down
38 changes: 1 addition & 37 deletions backend/deepchecks_monitoring/features_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,8 @@
class FeaturesSchema(BaseModel):
"""Schema to be returned to the client for the features control."""

max_models: int
signup_enabled: bool
slack_enabled: bool
rows_per_minute: int
custom_checks_enabled: bool
data_retention_months: int
monthly_predictions_limit: int
sso_enabled: bool
onboarding_enabled: bool
update_roles: bool
model_assignment: bool
Expand All @@ -36,12 +30,7 @@ class FeaturesControl:
def __init__(self, settings):
self.settings = settings

@property
def max_models(self) -> int:
"""Maximum number of models allowed for organization."""
return 1

async def get_allowed_models(self, session) -> int: # pylint: disable=unused-argument
async def get_allowed_models(self, session) -> int | None: # pylint: disable=unused-argument
"""For the cloud, number of models which are allowed by subscription."""
return 1

Expand Down Expand Up @@ -75,26 +64,6 @@ def rows_per_minute(self) -> int:
"""Maximum number of rows per minute allowed for organization."""
return 500_000

@property
def custom_checks_enabled(self) -> bool:
"""Whether custom checks are enabled."""
return False

@property
def data_retention_months(self) -> int:
"""Get number of months to keep data for."""
return 3

@property
def monthly_predictions_limit(self) -> int:
"""Maximum number of predictions per month allowed for organization."""
return 500_000

@property
def sso_enabled(self) -> bool:
"""Whether SSO is enabled."""
return False

@property
def multi_tenant(self) -> bool:
"""Whether multi-tenant is enabled."""
Expand All @@ -108,14 +77,9 @@ def email_enabled(self) -> bool:
def get_all_features(self) -> FeaturesSchema:
"""Get all features for the client."""
return FeaturesSchema(
max_models=self.max_models,
signup_enabled=self.signup_enabled,
slack_enabled=self.slack_enabled,
rows_per_minute=self.rows_per_minute,
custom_checks_enabled=self.custom_checks_enabled,
data_retention_months=self.data_retention_months,
monthly_predictions_limit=self.monthly_predictions_limit,
sso_enabled=self.sso_enabled,
onboarding_enabled=self.onboarding_enabled,
update_roles=self.update_roles,
model_assignment=self.model_assignment,
Expand Down
Loading
Loading