Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-metadata-fields-to-core-entities-api
Browse files Browse the repository at this point in the history
  • Loading branch information
novakzaballa committed May 13, 2024
2 parents 9bbdfe9 + 92e3e9f commit 100f8eb
Show file tree
Hide file tree
Showing 325 changed files with 7,232 additions and 6,329 deletions.
10 changes: 7 additions & 3 deletions .github/actions/e2e-bake-compose/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ runs:
password: ${{ inputs.github_token }}

- name: Bake Compose
working-directory: frontend
run: depot bake -f docker-compose-e2e-tests.yml --load
shell: bash
uses: nick-fields/retry@v3
with:
shell: bash
command: cd frontend && depot bake -f docker-compose-e2e-tests.yml --load
max_attempts: 2
retry_on: error
timeout_minutes: 10
18 changes: 11 additions & 7 deletions .github/actions/e2e-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,20 @@ runs:
shell: bash

- name: Test with Chromedriver
working-directory: frontend
uses: nick-fields/retry@v3
with:
shell: bash
command: |
cd frontend
node -v
npm run env
npm run test -- ${{ inputs.tests }}
max_attempts: 2
retry_on: error
timeout_minutes: 10
env:
E2E_TEST_TOKEN: ${{ inputs.e2e_test_token }}
SLACK_TOKEN: ${{ inputs.slack_token }}
STATIC_ASSET_CDN_URL: /
ENV: ${{ inputs.environment }}
GITHUB_ACTION_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

run: |
node -v
npm run env
npm run test -- ${{ inputs.tests }}
shell: bash
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repos:
exclude: migrations

- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 7.0.0
hooks:
- id: flake8
name: flake8
Expand Down
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.111.1"
".": "2.114.0"
}
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
# Changelog

## [2.114.0](https://github.com/Flagsmith/flagsmith/compare/v2.113.0...v2.114.0) (2024-05-10)


### Features

* add endpoint to revert v2 versioning ([#3897](https://github.com/Flagsmith/flagsmith/issues/3897)) ([da9e051](https://github.com/Flagsmith/flagsmith/commit/da9e051e72a1d50532fbf688d693980880f395c1))
* Implement GitHub Webhook ([#3906](https://github.com/Flagsmith/flagsmith/issues/3906)) ([9303267](https://github.com/Flagsmith/flagsmith/commit/9303267a078ecfb6788df51a8b3ff5fb83f67e8d))


### Bug Fixes

* Disable segment override diffs for non versioned environments ([#3914](https://github.com/Flagsmith/flagsmith/issues/3914)) ([e5b4313](https://github.com/Flagsmith/flagsmith/commit/e5b4313231bb3f882e2f61512933d9bb127c1a4d))
* Move call to GitHub integration tasks out from trigger_feature_state_change_webhooks ([#3905](https://github.com/Flagsmith/flagsmith/issues/3905)) ([dec9afa](https://github.com/Flagsmith/flagsmith/commit/dec9afab22019297e18ef6efb01c3398abeb9746))

## [2.113.0](https://github.com/Flagsmith/flagsmith/compare/v2.112.0...v2.113.0) (2024-05-09)


### Features

* Block access after seven days notice of API overage ([#3714](https://github.com/Flagsmith/flagsmith/issues/3714)) ([e2cb7eb](https://github.com/Flagsmith/flagsmith/commit/e2cb7eb003513a218e60c9d926988a8adaa0d565))
* versioned segment override change request ([#3790](https://github.com/Flagsmith/flagsmith/issues/3790)) ([cf320b7](https://github.com/Flagsmith/flagsmith/commit/cf320b7d030db0819ba784146da7e42220b154cf))


### Bug Fixes

* codehelp docs links ([#3900](https://github.com/Flagsmith/flagsmith/issues/3900)) ([5f7d3cd](https://github.com/Flagsmith/flagsmith/commit/5f7d3cd6a604c439ba586614065c8da605dc06f1))
* **docker:** Run Task Processor entrypoint with PID 1 ([#3889](https://github.com/Flagsmith/flagsmith/issues/3889)) ([79f4ef7](https://github.com/Flagsmith/flagsmith/commit/79f4ef7bbec47dcc1e315b3db7ae625b1bb91e38))
* Ensure flags are set in code example ([#3901](https://github.com/Flagsmith/flagsmith/issues/3901)) ([fa46ba7](https://github.com/Flagsmith/flagsmith/commit/fa46ba75aa050af6c05a2fe3925c2fed873687b8))
* send all users on paid subscriptions to hubspot ([#3902](https://github.com/Flagsmith/flagsmith/issues/3902)) ([0c79870](https://github.com/Flagsmith/flagsmith/commit/0c798702a313e86d62c1c4e9c7af019969eae117))

## [2.112.0](https://github.com/Flagsmith/flagsmith/compare/v2.111.1...v2.112.0) (2024-05-07)


### Features

* Clone identities (FE) ([#3725](https://github.com/Flagsmith/flagsmith/issues/3725)) ([084d775](https://github.com/Flagsmith/flagsmith/commit/084d7756864e72ef020554380e175238c39d5b3b))
* sort by Overage in sales dashboard ([#3858](https://github.com/Flagsmith/flagsmith/issues/3858)) ([2417f57](https://github.com/Flagsmith/flagsmith/commit/2417f57a037a2a6444fa2604d38dc49ffbff234c))


### Bug Fixes

* Change some texts in the cloning Identities flow ([#3862](https://github.com/Flagsmith/flagsmith/issues/3862)) ([57313ca](https://github.com/Flagsmith/flagsmith/commit/57313ca6802321688b7c735d8284f540188c4d9f))
* For Hubspot make the switch to unique org id ([#3863](https://github.com/Flagsmith/flagsmith/issues/3863)) ([54c2603](https://github.com/Flagsmith/flagsmith/commit/54c2603bbe7bef1bf3e520c6b6ae78c9c16b9458))
* Organisation can't have a new Github integration when had a prior one deleted ([#3874](https://github.com/Flagsmith/flagsmith/issues/3874)) ([53e728a](https://github.com/Flagsmith/flagsmith/commit/53e728a588cd193c8ea352d1b99ce7157d6cf2ef))
* typo ([#3861](https://github.com/Flagsmith/flagsmith/issues/3861)) ([29ae2e9](https://github.com/Flagsmith/flagsmith/commit/29ae2e995f794519cc6f5bb2fa22ebb5ba078650))
* update secrets location for GITHUB_PEM ([#3868](https://github.com/Flagsmith/flagsmith/issues/3868)) ([6e8d7b7](https://github.com/Flagsmith/flagsmith/commit/6e8d7b768871fa57f871b24c92b10f55ec43233a))
* use ENABLE_FLAGSMITH_REALTIME environment var ([#3867](https://github.com/Flagsmith/flagsmith/issues/3867)) ([41a8aa3](https://github.com/Flagsmith/flagsmith/commit/41a8aa3dd133424adef69b3f1b4ac0ae0df0bba8))
* **versioning:** feature segments updated with version ([#3880](https://github.com/Flagsmith/flagsmith/issues/3880)) ([08d4046](https://github.com/Flagsmith/flagsmith/commit/08d4046d05bfb278851b4a6bb0b3a7956a3f018d))
* **versioning:** prevent deleted segment overrides returning ([#3850](https://github.com/Flagsmith/flagsmith/issues/3850)) ([41981d4](https://github.com/Flagsmith/flagsmith/commit/41981d432b8adbb515d0bda2de3bdeb290a9276e))

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


Expand Down
3 changes: 3 additions & 0 deletions api/api/urls/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from environments.identities.views import SDKIdentities
from environments.sdk.views import SDKEnvironmentAPIView
from features.views import SDKFeatureStates
from integrations.github.views import github_webhook
from organisations.views import chargebee_webhook

schema_view = get_schema_view(
Expand Down Expand Up @@ -44,6 +45,8 @@
url(r"^metadata/", include("metadata.urls")),
# Chargebee webhooks
url(r"cb-webhook/", chargebee_webhook, name="chargebee-webhook"),
# GitHub integration webhook
url(r"github-webhook/", github_webhook, name="github-webhook"),
# Client SDK urls
url(r"^flags/$", SDKFeatureStates.as_view(), name="flags"),
url(r"^identities/$", SDKIdentities.as_view(), name="sdk-identities"),
Expand Down
4 changes: 2 additions & 2 deletions api/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,9 +874,9 @@
FLAGSMITH_ANALYTICS = env.bool("FLAGSMITH_ANALYTICS", default=False)
FLAGSMITH_ON_FLAGSMITH_API_URL = env("FLAGSMITH_ON_FLAGSMITH_API_URL", default=None)
FLAGSMITH_ON_FLAGSMITH_API_KEY = env("FLAGSMITH_ON_FLAGSMITH_API_KEY", default=None)
LINKEDIN_PARTNER_TRACKING = env("LINKEDIN_PARTNER_TRACKING", default=False)
GOOGLE_ANALYTICS_API_KEY = env("GOOGLE_ANALYTICS_API_KEY", default=None)
HEADWAY_API_KEY = env("HEADWAY_API_KEY", default=None)
LINKEDIN_API_KEY = env("LINKEDIN_API_KEY", default=None)
CRISP_CHAT_API_KEY = env("CRISP_CHAT_API_KEY", default=None)
MIXPANEL_API_KEY = env("MIXPANEL_API_KEY", default=None)
SENTRY_API_KEY = env("SENTRY_API_KEY", default=None)
Expand All @@ -893,7 +893,7 @@
# GitHub integrations
GITHUB_PEM = env.str("GITHUB_PEM", default="")
GITHUB_APP_ID: int = env.int("GITHUB_APP_ID", default=0)

GITHUB_WEBHOOK_SECRET = env.str("GITHUB_WEBHOOK_SECRET", default="")

# MailerLite
MAILERLITE_BASE_URL = env.str(
Expand Down
2 changes: 1 addition & 1 deletion api/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def project_overrides(request):
"fpr": "FIRST_PROMOTER_ID",
"headway": "HEADWAY_API_KEY",
"hideInviteLinks": "DISABLE_INVITE_LINKS",
"linkedin_api_key": "LINKEDIN_API_KEY",
"linkedinPartnerTracking": "LINKEDIN_PARTNER_TRACKING",
"maintenance": "MAINTENANCE_MODE",
"mixpanel": "MIXPANEL_API_KEY",
"preventEmailPassword": "PREVENT_EMAIL_PASSWORD",
Expand Down
44 changes: 42 additions & 2 deletions api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pytest_django.plugin import blocking_manager_key
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
from urllib3.connectionpool import HTTPConnectionPool
from xdist import get_xdist_worker_id

from api_keys.models import MasterAPIKey
Expand Down Expand Up @@ -62,6 +63,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 +81,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 @@ -115,6 +121,38 @@ def django_db_setup(request: pytest.FixtureRequest) -> None:
db_settings["NAME"] = test_db_name


@pytest.fixture(autouse=True)
def restrict_http_requests(monkeypatch: pytest.MonkeyPatch) -> None:
"""
This fixture prevents all tests from performing HTTP requests to
any host than `localhost`.
Any external request attempt leads to `RuntimeError` with a helpful message
pointing developers to the `responses` fixture.
"""
allowed_hosts = {"localhost"}
original_urlopen = HTTPConnectionPool.urlopen

def urlopen_mock(
self,
method: str,
url: str,
*args,
**kwargs,
) -> HTTPConnectionPool.ResponseCls:
if self.host in allowed_hosts:
return original_urlopen(self, method, url, *args, **kwargs)

raise RuntimeError(
f"Blocked {method} request to {self.scheme}://{self.host}{url}. "
"Use `responses` fixture to mock the response!"
)

monkeypatch.setattr(
"urllib3.connectionpool.HTTPConnectionPool.urlopen", urlopen_mock
)


trait_key = "key1"
trait_value = "value1"

Expand Down Expand Up @@ -298,11 +336,13 @@ def with_environment_permissions(
"""

def _with_environment_permissions(
permission_keys: list[str], environment_id: int | None = None
permission_keys: list[str],
environment_id: int | None = None,
admin: bool = False,
) -> UserEnvironmentPermission:
environment_id = environment_id or environment.id
uep, __ = UserEnvironmentPermission.objects.get_or_create(
environment_id=environment_id, user=staff_user
environment_id=environment_id, user=staff_user, defaults={"admin": admin}
)
uep.permissions.add(*permission_keys)

Expand Down
66 changes: 35 additions & 31 deletions api/edge_api/identities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from environments.models import Environment
from features.models import FeatureState
from features.multivariate.models import MultivariateFeatureStateValue
from features.versioning.versioning_service import get_environment_flags_dict
from users.models import FFAdminUser
from util.mappers import map_engine_identity_to_identity_document

Expand Down Expand Up @@ -80,41 +81,43 @@ def get_all_feature_states(
)
django_environment = Environment.objects.get(api_key=self.environment_api_key)

q = (
Q(version__isnull=False)
& Q(identity__isnull=True)
& (
Q(feature_segment__segment__id__in=segment_ids)
| Q(feature_segment__isnull=True)
)
# since identity overrides are included in the document retrieved from dynamo,
# we only want to retrieve the environment default and (relevant) segment overrides
# from the ORM.
additional_filters = Q(identity__isnull=True) & (
Q(feature_segment__segment__id__in=segment_ids)
| Q(feature_segment__isnull=True)
)
environment_and_segment_feature_states = (
django_environment.feature_states.select_related(
"feature",
"feature_segment",
"feature_segment__segment",
"feature_state_value",
)
.prefetch_related(
Prefetch(
"multivariate_feature_state_values",
queryset=MultivariateFeatureStateValue.objects.select_related(
"multivariate_feature_option"
),
)

feature_states: dict[str, FeatureState | FeatureStateModel] = (
get_environment_flags_dict(
environment=django_environment,
additional_filters=additional_filters,
additional_select_related_args=[
"feature",
"feature_segment",
"feature_segment__segment",
"feature_state_value",
],
additional_prefetch_related_args=[
Prefetch(
"multivariate_feature_state_values",
queryset=MultivariateFeatureStateValue.objects.select_related(
"multivariate_feature_option"
),
)
],
# since we only want to retrieve the highest priority feature state,
# we key off the feature name instead of the default
# (feature_id, segment_id, identity_id). This will give us only e.g.
# the highest priority matching segment override for a given feature.
key_function=lambda fs: fs.feature.name,
)
.filter(q)
)

feature_states = {}
for feature_state in environment_and_segment_feature_states:
feature_name = feature_state.feature.name
if (
feature_name not in feature_states
or feature_state > feature_states[feature_name]
):
feature_states[feature_name] = feature_state

# Since the identity overrides are the highest priority, we can now iterate
# over the dictionary and replace any feature states with those that have
# an identity override, stored against the identity in dynamo.
identity_feature_states = self.feature_overrides
identity_feature_names = set()
for identity_feature_state in identity_feature_states:
Expand Down Expand Up @@ -256,5 +259,6 @@ def clone_flag_states_from(self, source_identity: "EdgeIdentity") -> None:
feature=feature_in_source.feature,
feature_state_value=feature_in_source.feature_state_value,
enabled=feature_in_source.enabled,
multivariate_feature_state_values=feature_in_source.multivariate_feature_state_values,
)
self.add_feature_override(feature_state_target)
6 changes: 0 additions & 6 deletions api/environments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
AFTER_DELETE,
AFTER_SAVE,
AFTER_UPDATE,
BEFORE_UPDATE,
LifecycleModel,
hook,
)
Expand All @@ -44,7 +43,6 @@
from environments.exceptions import EnvironmentHeaderNotPresentError
from environments.managers import EnvironmentManager
from features.models import Feature, FeatureSegment, FeatureState
from features.versioning.exceptions import FeatureVersioningError
from metadata.models import Metadata
from projects.models import IdentityOverridesV2MigrationStatus, Project
from segments.models import Segment
Expand Down Expand Up @@ -150,10 +148,6 @@ def clear_environment_cache(self):
# TODO: this could rebuild the cache itself (using an async task)
environment_cache.delete(self.initial_value("api_key"))

@hook(BEFORE_UPDATE, when="use_v2_feature_versioning", was=True, is_now=False)
def validate_use_v2_feature_versioning(self):
raise FeatureVersioningError("Cannot revert from v2 feature versioning.")

@hook(AFTER_DELETE)
def delete_from_dynamo(self):
if self.project.enable_dynamo_db and environment_wrapper.is_enabled:
Expand Down
17 changes: 16 additions & 1 deletion api/environments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
NestedEnvironmentPermissions,
)
from environments.sdk.schemas import SDKEnvironmentDocumentModel
from features.versioning.tasks import enable_v2_versioning
from features.versioning.tasks import (
disable_v2_versioning,
enable_v2_versioning,
)
from permissions.permissions_calculator import get_environment_permission_data
from permissions.serializers import (
PermissionModelSerializer,
Expand Down Expand Up @@ -216,6 +219,18 @@ def enable_v2_versioning(self, request: Request, api_key: str) -> Response:
enable_v2_versioning.delay(kwargs={"environment_id": environment.id})
return Response(status=status.HTTP_202_ACCEPTED)

@swagger_auto_schema(request_body=no_body, responses={202: ""})
@action(detail=True, methods=["POST"], url_path="disable-v2-versioning")
def disable_v2_versioning(self, request: Request, api_key: str) -> Response:
environment = self.get_object()
if environment.use_v2_feature_versioning is False:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data={"detail": "Environment is not using v2 versioning."},
)
disable_v2_versioning.delay(kwargs={"environment_id": environment.id})
return Response(status=status.HTTP_202_ACCEPTED)


class NestedEnvironmentViewSet(viewsets.GenericViewSet):
model_class = None
Expand Down
Loading

0 comments on commit 100f8eb

Please sign in to comment.