Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(feature flags): copy cohorts linked to a flag #18642

Merged
merged 68 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
5d5a2f5
only users with edit rights can copy
jurajmajerik Nov 13, 2023
c09de36
allow to view projects to all, show a banner when copy not allowed
jurajmajerik Nov 13, 2023
cd7cafa
Update query snapshots
github-actions[bot] Nov 13, 2023
7df03c4
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
6424c7e
Update UI snapshots for `chromium` (1)
github-actions[bot] Nov 13, 2023
a0942c6
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
c132991
Merge branch 'master' of https://github.com/PostHog/posthog into mult…
jurajmajerik Nov 13, 2023
bfc5306
resolve conflict
jurajmajerik Nov 13, 2023
6fcb885
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
31be0e8
Update query snapshots
github-actions[bot] Nov 13, 2023
7d61925
Update UI snapshots for `chromium` (1)
github-actions[bot] Nov 13, 2023
630941d
Update query snapshots
github-actions[bot] Nov 13, 2023
4222978
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
34cad1e
Update UI snapshots for `chromium` (1)
github-actions[bot] Nov 13, 2023
e105deb
Update query snapshots
github-actions[bot] Nov 13, 2023
1f3115d
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
dbc372e
Update query snapshots
github-actions[bot] Nov 13, 2023
5404e3b
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
2f17b82
add columns to the table
jurajmajerik Nov 13, 2023
fe512b6
Merge branch 'multi-project-feature-flags' of https://github.com/Post…
jurajmajerik Nov 13, 2023
405723e
resolve conflict
jurajmajerik Nov 13, 2023
3b6348c
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
b69e17d
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
1d5e178
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
7621dc7
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 13, 2023
8f7b147
update test
jurajmajerik Nov 13, 2023
df96b98
Merge branch 'multi-project-feature-flags' of https://github.com/Post…
jurajmajerik Nov 13, 2023
282e78a
Merge branch 'master' of https://github.com/PostHog/posthog into mult…
jurajmajerik Nov 13, 2023
005b425
add back expected_data assertion in test_get_feature_flag_success
jurajmajerik Nov 14, 2023
0aec4c5
fix conflict
jurajmajerik Nov 14, 2023
3760a4d
Merge branch 'master' of https://github.com/PostHog/posthog into mult…
jurajmajerik Nov 14, 2023
97b588d
implement copying of cohorts
jurajmajerik Nov 15, 2023
4105c15
clean up
jurajmajerik Nov 15, 2023
d6a4e26
fix
jurajmajerik Nov 15, 2023
4366b9d
check destination cohort not overridden
jurajmajerik Nov 15, 2023
eb662f6
add extra test case
jurajmajerik Nov 15, 2023
6d402f3
Merge branch 'master' of https://github.com/PostHog/posthog into mult…
jurajmajerik Nov 15, 2023
8a56dda
fix test name
jurajmajerik Nov 15, 2023
b749843
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 15, 2023
319dd8d
add type for name_to_dest_cohort
jurajmajerik Nov 15, 2023
a80bb45
Merge branch 'multi-project-feature-flags' of https://github.com/Post…
jurajmajerik Nov 15, 2023
c9a893d
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 15, 2023
7c8e502
Update query snapshots
github-actions[bot] Nov 15, 2023
9cba0b0
Update query snapshots
github-actions[bot] Nov 15, 2023
e99640f
Update query snapshots
github-actions[bot] Nov 15, 2023
96fc253
Update query snapshots
github-actions[bot] Nov 15, 2023
7d60484
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 15, 2023
194e708
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 15, 2023
5c464ad
fix typing errors
jurajmajerik Nov 15, 2023
d3668c6
change seen_cohorts_cache key to int
jurajmajerik Nov 15, 2023
adc4c74
more type checking
jurajmajerik Nov 15, 2023
24ed496
test get_sorted_cohort_ids
jurajmajerik Nov 16, 2023
d64ab2c
more typing + clearer naming
jurajmajerik Nov 16, 2023
729348f
check the destination FF includes the right cohort(s)
jurajmajerik Nov 16, 2023
36434f7
mutate prop group using Filter model
jurajmajerik Nov 16, 2023
7d94584
fix conflict
jurajmajerik Nov 16, 2023
edfc58e
conflict
jurajmajerik Nov 16, 2023
5ec9fd7
Update UI snapshots for `chromium` (2)
github-actions[bot] Nov 16, 2023
315520a
Merge branch 'master' of https://github.com/PostHog/posthog into mult…
jurajmajerik Nov 16, 2023
25534c6
Merge branch 'master' of https://github.com/PostHog/posthog into mult…
jurajmajerik Nov 17, 2023
19a0e17
remove files
jurajmajerik Nov 17, 2023
6201ab4
revert cohort ids back to str
jurajmajerik Nov 17, 2023
50c74aa
fix test
jurajmajerik Nov 17, 2023
4653c6f
fix
jurajmajerik Nov 17, 2023
390d23f
serialize/deserialize original cohort properties
jurajmajerik Nov 17, 2023
a0152b6
add test for empty cohorts
jurajmajerik Nov 17, 2023
5bd1ff1
check prop is instance of dict
jurajmajerik Nov 17, 2023
629d9d6
Merge branch 'master' of https://github.com/PostHog/posthog into mult…
jurajmajerik Nov 17, 2023
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
4 changes: 3 additions & 1 deletion frontend/src/scenes/feature-flags/featureFlagLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,9 @@ export const featureFlagLogic = kea<featureFlagLogicType>([
: 'copied'
lemonToast.success(`Feature flag ${operation} successfully!`)
} else {
lemonToast.error(`Error while saving feature flag: ${featureFlagCopy?.failed || featureFlagCopy}`)
lemonToast.error(
`Error while saving feature flag: ${JSON.stringify(featureFlagCopy?.failed) || featureFlagCopy}`
)
}

actions.loadProjectsWithCurrentFlag()
Expand Down
83 changes: 73 additions & 10 deletions posthog/api/organization_feature_flag.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
from posthog.api.routing import StructuredViewSetMixin
from posthog.api.feature_flag import FeatureFlagSerializer
from posthog.api.feature_flag import CanEditFeatureFlag
from posthog.models import FeatureFlag, Team
from posthog.permissions import OrganizationMemberPermissions
from typing import Dict
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
Expand All @@ -12,6 +8,14 @@
viewsets,
status,
)
from posthog.api.cohort import CohortSerializer
from posthog.api.routing import StructuredViewSetMixin
from posthog.api.feature_flag import FeatureFlagSerializer
from posthog.api.feature_flag import CanEditFeatureFlag
from posthog.models import FeatureFlag, Team
from posthog.models.cohort import Cohort
from posthog.models.filters.filter import Filter
from posthog.permissions import OrganizationMemberPermissions


class OrganizationFeatureFlagView(
Expand Down Expand Up @@ -86,7 +90,7 @@ def copy_flags(self, request, *args, **kwargs):
for target_project_id in target_project_ids:
# Target project does not exist
try:
Team.objects.get(id=target_project_id)
target_project = Team.objects.get(id=target_project_id)
except ObjectDoesNotExist:
failed_projects.append(
{
Expand All @@ -96,10 +100,65 @@ def copy_flags(self, request, *args, **kwargs):
)
continue

context = {
"request": request,
"team_id": target_project_id,
}
# get all linked cohorts, sorted by creation order
seen_cohorts_cache: Dict[str, Cohort] = {}
sorted_cohort_ids = flag_to_copy.get_cohort_ids(
seen_cohorts_cache=seen_cohorts_cache, sort_by_topological_order=True
)

# destination cohort id is different from original cohort id - create mapping
name_to_dest_cohort_id: Dict[str, int] = {}
# create cohorts in the destination project
if len(sorted_cohort_ids):
for cohort_id in sorted_cohort_ids:
original_cohort = seen_cohorts_cache[str(cohort_id)]

# search in destination project by name
destination_cohort = Cohort.objects.filter(
name=original_cohort.name, team_id=target_project_id, deleted=False
).first()

# create new cohort in the destination project
if not destination_cohort:
prop_group = Filter(
data={"properties": original_cohort.properties.to_dict(), "is_simplified": True}
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
).property_groups

for prop in prop_group.flat:
if prop.type == "cohort":
original_child_cohort_id = prop.value
original_child_cohort = seen_cohorts_cache[str(original_child_cohort_id)]
prop.value = name_to_dest_cohort_id[original_child_cohort.name]

destination_cohort_serializer = CohortSerializer(
data={
"team": target_project,
"name": original_cohort.name,
"groups": [],
"filters": {"properties": prop_group.to_dict()},
"description": original_cohort.description,
"is_static": original_cohort.is_static,
},
context={
"request": request,
"team_id": target_project.id,
},
)
destination_cohort_serializer.is_valid(raise_exception=True)
destination_cohort = destination_cohort_serializer.save()

if destination_cohort is not None:
name_to_dest_cohort_id[original_cohort.name] = destination_cohort.id

# reference correct destination cohort ids in the flag
for group in flag_to_copy.conditions:
props = group.get("properties", [])
for prop in props:
if isinstance(prop, dict) and prop.get("type") == "cohort":
original_cohort_id = prop["value"]
cohort_name = (seen_cohorts_cache[str(original_cohort_id)]).name
prop["value"] = name_to_dest_cohort_id[cohort_name]

flag_data = {
"key": flag_to_copy.key,
"name": flag_to_copy.name,
Expand All @@ -109,6 +168,10 @@ def copy_flags(self, request, *args, **kwargs):
"ensure_experience_continuity": flag_to_copy.ensure_experience_continuity,
"deleted": False,
}
context = {
"request": request,
"team_id": target_project_id,
}

existing_flag = FeatureFlag.objects.filter(
key=feature_flag_key, team_id=target_project_id, deleted=False
Expand Down
73 changes: 73 additions & 0 deletions posthog/api/test/test_feature_flag_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import Dict, Set
from posthog.test.base import (
APIBaseTest,
)
from posthog.models.cohort import Cohort
from posthog.models.cohort.util import sort_cohorts_topologically


class TestFeatureFlagUtils(APIBaseTest):
def setUp(self):
super().setUp()

def test_cohorts_sorted_topologically(self):
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
cohorts = {}

def create_cohort(name):
cohorts[name] = Cohort.objects.create(
team=self.team,
name=name,
filters={
"properties": {
"type": "AND",
"values": [
{"key": "name", "value": "test", "type": "person"},
],
}
},
)

create_cohort("a")
create_cohort("b")
create_cohort("c")

# (c)-->(b)
cohorts["c"].filters["properties"]["values"][0] = {
"key": "id",
"value": cohorts["b"].pk,
"type": "cohort",
"negation": True,
}
cohorts["c"].save()

# (a)-->(c)
cohorts["a"].filters["properties"]["values"][0] = {
"key": "id",
"value": cohorts["c"].pk,
"type": "cohort",
"negation": True,
}
cohorts["a"].save()

cohort_ids = {cohorts["a"].pk, cohorts["b"].pk, cohorts["c"].pk}
seen_cohorts_cache = {
str(cohorts["a"].pk): cohorts["a"],
str(cohorts["b"].pk): cohorts["b"],
str(cohorts["c"].pk): cohorts["c"],
}

# (a)-->(c)-->(b)
# create b first, since it doesn't depend on any other cohorts
# then c, because it depends on b
# then a, because it depends on c

# thus destination creation order: b, c, a
destination_creation_order = [cohorts["b"].pk, cohorts["c"].pk, cohorts["a"].pk]
topologically_sorted_cohort_ids = sort_cohorts_topologically(cohort_ids, seen_cohorts_cache)
self.assertEqual(topologically_sorted_cohort_ids, destination_creation_order)

def test_empty_cohorts_set(self):
cohort_ids: Set[int] = set()
seen_cohorts_cache: Dict[str, Cohort] = {}
topologically_sorted_cohort_ids = sort_cohorts_topologically(cohort_ids, seen_cohorts_cache)
self.assertEqual(topologically_sorted_cohort_ids, [])
Loading
Loading