Skip to content

Commit

Permalink
Merge branch 'main' into feat/add_api_usage_billing
Browse files Browse the repository at this point in the history
  • Loading branch information
zachaysan committed May 3, 2024
2 parents 33251c1 + 53e728a commit 7288d49
Show file tree
Hide file tree
Showing 71 changed files with 2,038 additions and 1,294 deletions.
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.110.2"
".": "2.111.1"
}
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## [2.111.1](https://github.com/Flagsmith/flagsmith/compare/v2.111.0...v2.111.1) (2024-04-30)


### Bug Fixes

* edit group ([#3856](https://github.com/Flagsmith/flagsmith/issues/3856)) ([b25e6f8](https://github.com/Flagsmith/flagsmith/commit/b25e6f8ad1256b5556cfc7623064a6fd42b299fb))

## [2.111.0](https://github.com/Flagsmith/flagsmith/compare/v2.110.2...v2.111.0) (2024-04-30)


### Features

* Capability for Pydantic-based OpenAPI response schemas ([#3795](https://github.com/Flagsmith/flagsmith/issues/3795)) ([609deaa](https://github.com/Flagsmith/flagsmith/commit/609deaa999c86bc05e1875c58f91c7c34532f3c5))
* **permissions:** manage permissions from a single location ([#3730](https://github.com/Flagsmith/flagsmith/issues/3730)) ([fc34a53](https://github.com/Flagsmith/flagsmith/commit/fc34a53e92ba6c1870ab9539bfa21b6af08964cc))


### Bug Fixes

* Add GitHub app URL to env var ([#3847](https://github.com/Flagsmith/flagsmith/issues/3847)) ([210dbf7](https://github.com/Flagsmith/flagsmith/commit/210dbf7543dfddcbc36422eec0be958d6e8d6589))
* Filter versioned features ([#3756](https://github.com/Flagsmith/flagsmith/issues/3756)) ([686e1ab](https://github.com/Flagsmith/flagsmith/commit/686e1ab81aae938a4a85680a6ed581d90b7ff11d))
* Get current api usage InfluxDB query ([#3846](https://github.com/Flagsmith/flagsmith/issues/3846)) ([905c9fb](https://github.com/Flagsmith/flagsmith/commit/905c9fb36a67171464f03fd9565f02db74708974))
* **hubspot:** create hubspot company with domain ([#3844](https://github.com/Flagsmith/flagsmith/issues/3844)) ([d4c9173](https://github.com/Flagsmith/flagsmith/commit/d4c9173c6c371546f581f2c81572fa2c7395dd17))
* **sentry-FLAGSMITH-API-4BN:** update permission method ([#3851](https://github.com/Flagsmith/flagsmith/issues/3851)) ([b4e058a](https://github.com/Flagsmith/flagsmith/commit/b4e058a47c807455aefcd846f7a7f48de8c4a320))
* useHasPermission import ([#3853](https://github.com/Flagsmith/flagsmith/issues/3853)) ([e156609](https://github.com/Flagsmith/flagsmith/commit/e156609570782fa9edbdf52e9e01e627e9bf8ffc))
* user delete social auth ([#3693](https://github.com/Flagsmith/flagsmith/issues/3693)) ([3372207](https://github.com/Flagsmith/flagsmith/commit/3372207a8db623a6a07ac5df0902f24b8c0b1e4a))

## [2.110.2](https://github.com/Flagsmith/flagsmith/compare/v2.110.1...v2.110.2) (2024-04-25)


Expand Down
8 changes: 6 additions & 2 deletions api/api_keys/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ def get_permitted_projects(
)

def get_permitted_environments(
self, permission_key: str, project: "Project", tag_ids: typing.List[int] = None
self,
permission_key: str,
project: "Project",
tag_ids: typing.List[int] = None,
prefetch_metadata: bool = False,
) -> QuerySet["Environment"]:
return get_permitted_environments_for_master_api_key(
self.key, project, permission_key, tag_ids
self.key, project, permission_key, tag_ids, prefetch_metadata
)
6 changes: 6 additions & 0 deletions api/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,12 @@
default=None,
)

# The URL used to install the GitHub integration
GITHUB_APP_URL = env.int(
"GITHUB_APP_URL",
default=None,
)

# LDAP setting
LDAP_INSTALLED = importlib.util.find_spec("flagsmith_ldap")
# The URL of the LDAP server.
Expand Down
1 change: 1 addition & 0 deletions api/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def project_overrides(request):
"sentry": "SENTRY_API_KEY",
"useSecureCookies": "USE_SECURE_COOKIES",
"cookieSameSite": "COOKIE_SAME_SITE",
"githubAppURL": "GITHUB_APP_URL",
}

override_data = {
Expand Down
6 changes: 2 additions & 4 deletions api/app_analytics/influxdb_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ def get_current_api_usage(organisation_id: int, date_range: str) -> int:
),
drop_columns=("_start", "_stop", "_time"),
extra='|> sum() \
|> group() \
|> sort(columns: ["_value"], desc: true) ',
)

Expand All @@ -349,10 +350,7 @@ def get_current_api_usage(organisation_id: int, date_range: str) -> int:
if len(result.records) == 0:
return 0

# There should only be one matching result due to the
# sum part of the query.
assert len(result.records) == 1
return result.records[0].get_value()
return sum(r.get_value() for r in result.records)

return 0

Expand Down
43 changes: 42 additions & 1 deletion api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
from projects.tags.models import Tag
from segments.models import Condition, Segment, SegmentRule
from task_processor.task_run_method import TaskRunMethod
from tests.test_helpers import fix_issue_3869
from tests.types import (
WithEnvironmentPermissionsCallable,
WithOrganisationPermissionsCallable,
Expand All @@ -79,6 +80,10 @@ def pytest_addoption(parser: pytest.Parser) -> None:
)


def pytest_sessionstart(session: pytest.Session) -> None:
fix_issue_3869()


@pytest.hookimpl(trylast=True)
def pytest_configure(config: pytest.Config) -> None:
if (
Expand Down Expand Up @@ -130,12 +135,32 @@ def auth_token(test_user):


@pytest.fixture()
def admin_client(admin_user):
def admin_client_original(admin_user):
client = APIClient()
client.force_authenticate(user=admin_user)
return client


@pytest.fixture()
def admin_client(admin_client_original):
"""
This fixture will eventually be switched over to what is now
called admin_client_new which will run an admin client as well
as admin_master_api_key_client automatically.
In the meantime consider this fixture as deprecated. Use either
admin_client_original to preserve a singular admin client or
if the test suite can handle it, use admin_client_new to
automatically handling both query methods.
If a test must use pytest.mark.parametrize to differentiate
between other required parameters for a test then please
use admin_client_original as the parametrized version as this
fixture will ultimately be updated to the new approach.
"""
yield admin_client_original


@pytest.fixture()
def test_user_client(api_client, test_user):
api_client.force_authenticate(test_user)
Expand Down Expand Up @@ -344,6 +369,7 @@ def _with_project_permissions(
@pytest.fixture()
def environment_v2_versioning(environment):
enable_v2_versioning(environment.id)
environment.refresh_from_db()
return environment


Expand Down Expand Up @@ -774,3 +800,18 @@ def github_repository(
repository_name="repositorynametest",
project=project,
)


@pytest.fixture(
params=[
"admin_client_original",
"admin_master_api_key_client",
]
)
def admin_client_new(request, admin_client_original, admin_master_api_key_client):
if request.param == "admin_client_original":
yield admin_client_original
elif request.param == "admin_master_api_key_client":
yield admin_master_api_key_client
else:
assert False, "Request param mismatch"
2 changes: 2 additions & 0 deletions api/custom_auth/constants.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
USER_REGISTRATION_WITHOUT_INVITE_ERROR_MESSAGE = (
"User registration without an invite is disabled for this installation."
)
INVALID_PASSWORD_ERROR = "Invalid password."
FIELD_BLANK_ERROR = "This field may not be blank."
40 changes: 37 additions & 3 deletions api/custom_auth/serializers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
from django.conf import settings
from djoser.serializers import UserCreateSerializer, UserDeleteSerializer
from djoser.serializers import UserCreateSerializer
from rest_framework import serializers
from rest_framework.authtoken.models import Token
from rest_framework.exceptions import PermissionDenied
from rest_framework.validators import UniqueValidator

from organisations.invites.models import Invite
from users.auth_type import AuthType
from users.constants import DEFAULT_DELETE_ORPHAN_ORGANISATIONS_VALUE
from users.models import FFAdminUser, SignUpType

from .constants import USER_REGISTRATION_WITHOUT_INVITE_ERROR_MESSAGE
from .constants import (
FIELD_BLANK_ERROR,
INVALID_PASSWORD_ERROR,
USER_REGISTRATION_WITHOUT_INVITE_ERROR_MESSAGE,
)


class CustomTokenSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -72,7 +77,36 @@ def save(self, **kwargs):
return super(CustomUserCreateSerializer, self).save(**kwargs)


class CustomUserDelete(UserDeleteSerializer):
class CustomUserDelete(serializers.Serializer):
current_password = serializers.CharField(
style={"input_type": "password"},
required=False,
allow_null=True,
allow_blank=True,
)

default_error_messages = {
"invalid_password": INVALID_PASSWORD_ERROR,
"field_blank": FIELD_BLANK_ERROR,
}

def validate_current_password(self, value):
user_auth_type = self.context["request"].user.auth_type
if (
user_auth_type == AuthType.GOOGLE.value
or user_auth_type == AuthType.GITHUB.value
):
return value

if not value:
return self.fail("field_blank")

is_password_valid = self.context["request"].user.check_password(value)
if is_password_valid:
return value
else:
self.fail("invalid_password")

delete_orphan_organisations = serializers.BooleanField(
default=DEFAULT_DELETE_ORPHAN_ORGANISATIONS_VALUE, required=False
)
26 changes: 19 additions & 7 deletions api/features/feature_external_resources/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from features.models import Feature, FeatureState
from integrations.github.github import GithubData, generate_data
from integrations.github.tasks import call_github_app_webhook_for_feature_state
from organisations.models import Organisation
from webhooks.webhooks import WebhookEventType

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -47,12 +48,18 @@ class Meta:
]

@hook(AFTER_SAVE)
def exectute_after_save_actions(self):
def execute_after_save_actions(self):
# Add a comment to GitHub Issue/PR when feature is linked to the GH external resource
if hasattr(self.feature.project.organisation, "github_config"):
github_configuration = self.feature.project.organisation.github_config

feature_states = FeatureState.objects.filter(feature_id=self.feature_id)
if (
github_configuration := Organisation.objects.prefetch_related(
"github_config"
)
.get(id=self.feature.project.organisation_id)
.github_config.first()
):
feature_states = FeatureState.objects.filter(
feature_id=self.feature_id, identity_id__isnull=True
)
feature_data: GithubData = generate_data(
github_configuration,
self.feature_id,
Expand All @@ -68,8 +75,13 @@ def exectute_after_save_actions(self):
@hook(BEFORE_DELETE)
def execute_before_save_actions(self) -> None:
# Add a comment to GitHub Issue/PR when feature is unlinked to the GH external resource
if hasattr(self.feature.project.organisation, "github_config"):
github_configuration = self.feature.project.organisation.github_config
if (
github_configuration := Organisation.objects.prefetch_related(
"github_config"
)
.get(id=self.feature.project.organisation_id)
.github_config.first()
):
feature_data: GithubData = generate_data(
github_configuration=github_configuration,
feature_id=self.feature_id,
Expand Down
25 changes: 19 additions & 6 deletions api/features/feature_external_resources/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

from django.db.utils import IntegrityError
from django.shortcuts import get_object_or_404
from rest_framework import status, viewsets
Expand All @@ -6,6 +8,7 @@

from features.models import Feature
from features.permissions import FeatureExternalResourcePermissions
from organisations.models import Organisation

from .models import FeatureExternalResource
from .serializers import FeatureExternalResourceSerializer
Expand All @@ -29,9 +32,15 @@ def create(self, request, *args, **kwargs):
),
)

if not hasattr(feature.project.organisation, "github_config") or not hasattr(
feature.project, "github_project"
if not (
(
Organisation.objects.prefetch_related("github_config")
.get(id=feature.project.organisation_id)
.github_config.first()
)
or not hasattr(feature.project, "github_project")
):

return Response(
data={
"detail": "This Project doesn't have a valid GitHub integration configuration"
Expand All @@ -42,10 +51,14 @@ def create(self, request, *args, **kwargs):

try:
return super().create(request, *args, **kwargs)
except IntegrityError:
raise ValidationError(
detail="Duplication error. The feature already has this resource URI"
)

except IntegrityError as e:
if re.search(r"Key \(feature_id, url\)", str(e)) and re.search(
r"already exists.$", str(e)
):
raise ValidationError(
detail="Duplication error. The feature already has this resource URI"
)

def perform_update(self, serializer):
external_resource_id = int(self.kwargs["id"])
Expand Down
10 changes: 6 additions & 4 deletions api/features/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from ordered_model.models import OrderedModelManager
from softdelete.models import SoftDeleteManager

from features.versioning.models import EnvironmentFeatureVersion

if typing.TYPE_CHECKING:
from environments.models import Environment
from features.models import FeatureState
Expand All @@ -31,11 +33,11 @@ def get_live_feature_states(

qs_filter = Q(environment=environment, deleted_at__isnull=True)
if environment.use_v2_feature_versioning:
qs_filter &= Q(
environment_feature_version__isnull=False,
environment_feature_version__published_at__isnull=False,
environment_feature_version__live_from__lte=now,
latest_versions = EnvironmentFeatureVersion.objects.get_latest_versions(
environment
)
latest_version_uuids = [efv.uuid for efv in latest_versions]
qs_filter &= Q(environment_feature_version__uuid__in=latest_version_uuids)
else:
qs_filter &= Q(
live_from__isnull=False,
Expand Down
Loading

0 comments on commit 7288d49

Please sign in to comment.