Skip to content

Commit

Permalink
Merge branch 'main' into feat/create_change_requests_for_segments
Browse files Browse the repository at this point in the history
  • Loading branch information
zachaysan committed Aug 19, 2024
2 parents cc7c753 + 33074f3 commit b52b2f7
Show file tree
Hide file tree
Showing 17 changed files with 403 additions and 94 deletions.
2 changes: 1 addition & 1 deletion api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ POETRY_VERSION ?= 1.8.3
GUNICORN_LOGGER_CLASS ?= util.logging.GunicornJsonCapableLogger

SAML_REVISION ?= v1.6.3
RBAC_REVISION ?= v0.7.0
RBAC_REVISION ?= v0.8.0

-include .env-local
-include $(DOTENV_OVERRIDE_FILE)
Expand Down
6 changes: 3 additions & 3 deletions api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from features.value_types import STRING
from features.versioning.tasks import enable_v2_versioning
from features.workflows.core.models import ChangeRequest
from integrations.github.models import GithubConfiguration, GithubRepository
from integrations.github.models import GithubConfiguration, GitHubRepository
from metadata.models import (
Metadata,
MetadataField,
Expand Down Expand Up @@ -1079,8 +1079,8 @@ def github_configuration(organisation: Organisation) -> GithubConfiguration:
def github_repository(
github_configuration: GithubConfiguration,
project: Project,
) -> GithubRepository:
return GithubRepository.objects.create(
) -> GitHubRepository:
return GitHubRepository.objects.create(
github_configuration=github_configuration,
repository_owner="repositoryownertest",
repository_name="repositorynametest",
Expand Down
15 changes: 13 additions & 2 deletions api/features/feature_external_resources/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import logging
import re

from django.db import models
from django.db.models import Q
Expand All @@ -14,7 +15,7 @@
from features.models import Feature, FeatureState
from integrations.github.constants import GitHubEventType, GitHubTag
from integrations.github.github import call_github_task
from integrations.github.models import GithubRepository
from integrations.github.models import GitHubRepository
from organisations.models import Organisation
from projects.tags.models import Tag, TagType

Expand Down Expand Up @@ -79,9 +80,19 @@ def execute_after_save_actions(self):
.get(id=self.feature.project.organisation_id)
.github_config.first()
):
github_repo = GithubRepository.objects.get(
if self.type == "GITHUB_PR":
pattern = r"github.com/([^/]+)/([^/]+)/pull/\d+$"
elif self.type == "GITHUB_ISSUE":
pattern = r"github.com/([^/]+)/([^/]+)/issues/\d+$"

url_match = re.search(pattern, self.url)
owner, repo = url_match.groups()

github_repo = GitHubRepository.objects.get(
github_configuration=github_configuration.id,
project=self.feature.project,
repository_owner=owner,
repository_name=repo,
)
if github_repo.tagging_enabled:
github_tag = Tag.objects.get(
Expand Down
4 changes: 2 additions & 2 deletions api/features/feature_external_resources/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
get_github_issue_pr_title_and_state,
label_github_issue_pr,
)
from integrations.github.models import GithubRepository
from integrations.github.models import GitHubRepository
from organisations.models import Organisation

from .models import FeatureExternalResource
Expand Down Expand Up @@ -85,7 +85,7 @@ def create(self, request, *args, **kwargs):
url_match = re.search(pattern, url)
if url_match:
owner, repo, issue = url_match.groups()
if GithubRepository.objects.get(
if GitHubRepository.objects.get(
github_configuration=github_configuration,
repository_owner=owner,
repository_name=repo,
Expand Down
8 changes: 4 additions & 4 deletions api/features/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,11 @@ def get_queryset(self):
identity__isnull=True,
feature_segment__isnull=True,
)
feature_states = FeatureState.objects.get_live_feature_states(
self.environment,
feature_states = get_environment_flags_list(
environment=self.environment,
additional_filters=q,
).select_related("feature_state_value", "feature")

additional_select_related_args=["feature_state_value", "feature"],
)
self._feature_states = {fs.feature_id: fs for fs in feature_states}

return queryset
Expand Down
20 changes: 17 additions & 3 deletions api/integrations/github/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,23 @@ def fetch_search_github_resource(
headers: dict[str, str] = build_request_headers(
github_configuration.installation_id
)
response = requests.get(url, headers=headers, timeout=GITHUB_API_CALLS_TIMEOUT)
response.raise_for_status()
json_response = response.json()
try:
response = requests.get(url, headers=headers, timeout=GITHUB_API_CALLS_TIMEOUT)
response.raise_for_status()
json_response = response.json()

except HTTPError:
response_content = response.content.decode("utf-8")
error_message = (
"The resources do not exist or you do not have permission to view them"
)
error_data = json.loads(response_content)
if error_data.get("message", "") == "Validation Failed" and any(
error.get("code", "") == "invalid" for error in error_data.get("errors", [])
):
logger.warning(error_message)
raise ValueError(error_message)

results = [
{
"html_url": i["html_url"],
Expand Down
2 changes: 1 addition & 1 deletion api/integrations/github/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Meta:
ordering = ("id",)


class GithubRepository(LifecycleModelMixin, SoftDeleteExportableModel):
class GitHubRepository(LifecycleModelMixin, SoftDeleteExportableModel):
github_configuration = models.ForeignKey(
GithubConfiguration, related_name="repository_config", on_delete=models.CASCADE
)
Expand Down
4 changes: 2 additions & 2 deletions api/integrations/github/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
PaginatedQueryParams,
RepoQueryParams,
)
from integrations.github.models import GithubConfiguration, GithubRepository
from integrations.github.models import GithubConfiguration, GitHubRepository


class GithubConfigurationSerializer(ModelSerializer):
Expand All @@ -19,7 +19,7 @@ class Meta:

class GithubRepositorySerializer(ModelSerializer):
class Meta:
model = GithubRepository
model = GitHubRepository
optional_fields = ("search_text", "page")
fields = (
"id",
Expand Down
6 changes: 3 additions & 3 deletions api/integrations/github/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
tag_by_event_type,
)
from integrations.github.helpers import github_webhook_payload_is_valid
from integrations.github.models import GithubConfiguration, GithubRepository
from integrations.github.models import GithubConfiguration, GitHubRepository
from integrations.github.permissions import HasPermissionToGithubConfiguration
from integrations.github.serializers import (
GithubConfigurationSerializer,
Expand Down Expand Up @@ -126,7 +126,7 @@ class GithubRepositoryViewSet(viewsets.ModelViewSet):
GithubIsAdminOrganisation,
)
serializer_class = GithubRepositorySerializer
model_class = GithubRepository
model_class = GitHubRepository

def perform_create(self, serializer):
github_configuration_id = self.kwargs["github_pk"]
Expand All @@ -136,7 +136,7 @@ def get_queryset(self):
try:
if github_pk := self.kwargs.get("github_pk"):
int(github_pk)
return GithubRepository.objects.filter(github_configuration=github_pk)
return GitHubRepository.objects.filter(github_configuration=github_pk)
except ValueError:
raise ValidationError({"github_pk": ["Must be an integer"]})

Expand Down
26 changes: 18 additions & 8 deletions api/organisations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,18 +303,14 @@ def save_as_free_subscription(self):
if not getattr(self.organisation, "subscription_information_cache", None):
return

# There is a weird bug where the cache is present, but the id is unset.
# See here for more: https://flagsmith.sentry.io/issues/4945988284/
if not self.organisation.subscription_information_cache.id:
return

self.organisation.subscription_information_cache.delete()
self.organisation.subscription_information_cache.reset_to_defaults()
self.organisation.subscription_information_cache.save()

def prepare_for_cancel(
self, cancellation_date=timezone.now(), update_chargebee=True
) -> None:
"""
This method get's a subscription ready for cancelation.
This method gets a subscription ready for cancellation.
If cancellation_date is in the future some aspects are
reserved for a task after the date has passed.
Expand Down Expand Up @@ -451,7 +447,7 @@ class OrganisationSubscriptionInformationCache(LifecycleModelMixin, models.Model
api_calls_7d = models.IntegerField(default=0)
api_calls_30d = models.IntegerField(default=0)

allowed_seats = models.IntegerField(default=1)
allowed_seats = models.IntegerField(default=MAX_SEATS_IN_FREE_PLAN)
allowed_30d_api_calls = models.IntegerField(default=MAX_API_CALLS_IN_FREE_PLAN)
allowed_projects = models.IntegerField(default=1, blank=True, null=True)

Expand All @@ -461,6 +457,20 @@ class OrganisationSubscriptionInformationCache(LifecycleModelMixin, models.Model
def erase_api_notifications(self):
self.organisation.api_usage_notifications.all().delete()

def reset_to_defaults(self):
"""
Resets all limits and CB related data to the defaults, leaving the
usage data intact.
"""
self.current_billing_term_starts_at = None
self.current_billing_term_ends_at = None

self.allowed_seats = MAX_SEATS_IN_FREE_PLAN
self.allowed_30d_api_calls = MAX_API_CALLS_IN_FREE_PLAN
self.allowed_projects = 1

self.chargebee_email = None


class OrganisationAPIUsageNotification(models.Model):
organisation = models.ForeignKey(
Expand Down
1 change: 1 addition & 0 deletions api/organisations/subscriptions/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def __init__(
api_calls: int = 0,
projects: typing.Optional[int] = None,
chargebee_email=None,
**kwargs, # allows for extra unknown attrs from CB json metadata
):
self.seats = seats
self.api_calls = api_calls
Expand Down
43 changes: 30 additions & 13 deletions api/organisations/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,17 @@ def charge_for_api_call_count_overages():
api_usage = get_current_api_usage(organisation.id)

# Grace period for organisations < 200% of usage.
if api_usage / subscription_cache.allowed_30d_api_calls < 2.0:
if (
not hasattr(organisation, "breached_grace_period")
and api_usage / subscription_cache.allowed_30d_api_calls < 2.0
):
logger.info("API Usage below normal usage or grace period.")

# Set organisation grace period breach for following months.
if api_usage / subscription_cache.allowed_30d_api_calls > 1.0:
OrganisationBreachedGracePeriod.objects.get_or_create(
organisation=organisation
)
continue

api_billings = OrganisationAPIBilling.objects.filter(
Expand All @@ -221,19 +230,27 @@ def charge_for_api_call_count_overages():
logger.info("API Usage below current API limit.")
continue

if organisation.subscription.plan in {SCALE_UP, SCALE_UP_V2}:
add_100k_api_calls_scale_up(
organisation.subscription.subscription_id,
math.ceil(api_overage / 100_000),
)
elif organisation.subscription.plan in {STARTUP, STARTUP_V2}:
add_100k_api_calls_start_up(
organisation.subscription.subscription_id,
math.ceil(api_overage / 100_000),
)
else:
try:
if organisation.subscription.plan in {SCALE_UP, SCALE_UP_V2}:
add_100k_api_calls_scale_up(
organisation.subscription.subscription_id,
math.ceil(api_overage / 100_000),
)
elif organisation.subscription.plan in {STARTUP, STARTUP_V2}:
add_100k_api_calls_start_up(
organisation.subscription.subscription_id,
math.ceil(api_overage / 100_000),
)
else:
logger.error(
f"Unable to bill for API overages for plan `{organisation.subscription.plan}` "
f"for organisation {organisation.id}"
)
continue
except Exception:
logger.error(
f"Unable to bill for API overages for plan `{organisation.subscription.plan}`"
f"Unable to charge organisation {organisation.id} due to billing error",
exc_info=True,
)
continue

Expand Down
Loading

0 comments on commit b52b2f7

Please sign in to comment.