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(scheduled flag changes): implement scheduling of changes #19395

Merged
merged 78 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
9d277aa
add plain UI
jurajmajerik Dec 5, 2023
7d49c4c
Update UI snapshots for `chromium` (1)
github-actions[bot] Dec 5, 2023
7d51dbc
resolve conflict
jurajmajerik Dec 6, 2023
2563319
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 6, 2023
8496142
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 6, 2023
f72db3d
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 7, 2023
1d5c0ac
add schedule changes API
jurajmajerik Dec 8, 2023
2be18e8
Merge branch 'master' of https://github.com/PostHog/posthog into feat…
jurajmajerik Dec 8, 2023
473b9bd
add migrations
jurajmajerik Dec 8, 2023
92b4bad
adjust model
jurajmajerik Dec 12, 2023
59d9e00
Merge branch 'master' of https://github.com/PostHog/posthog into feat…
jurajmajerik Dec 12, 2023
232175b
adjust
jurajmajerik Dec 12, 2023
845bce5
connect frontend to the API
jurajmajerik Dec 13, 2023
10132ef
resolve conflict
jurajmajerik Dec 14, 2023
8bff377
clarify
jurajmajerik Dec 14, 2023
d00e33d
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 14, 2023
e317449
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 14, 2023
20b2a8e
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 14, 2023
2c7be9e
Update query snapshots
github-actions[bot] Dec 14, 2023
4cd2bcf
Update UI snapshots for `chromium` (2)
github-actions[bot] Dec 14, 2023
20fe30a
Update query snapshots
github-actions[bot] Dec 14, 2023
60fd6a2
rename setId -> setFeatureFlagId
jurajmajerik Dec 14, 2023
7b0b30e
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 14, 2023
99099e2
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 15, 2023
a22e702
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 15, 2023
a5289e8
update latest migrations
jurajmajerik Dec 16, 2023
74f98dc
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 16, 2023
7978672
Merge branch 'master' of https://github.com/PostHog/posthog into feat…
jurajmajerik Dec 18, 2023
5c71062
Merge branch 'master' of https://github.com/PostHog/posthog into feat…
jurajmajerik Dec 18, 2023
60ea996
implement scheduling of changes
jurajmajerik Dec 18, 2023
2cc0f5d
don't re-run failed changes
jurajmajerik Dec 18, 2023
3286b74
Update posthog/celery.py
jurajmajerik Dec 19, 2023
ac1d83b
process in chunks
jurajmajerik Dec 19, 2023
5fde0f0
add return type
jurajmajerik Dec 20, 2023
f3c283a
order queried changes by scheduled_at
jurajmajerik Dec 20, 2023
5fe726e
add test for multiple scheduled changes
jurajmajerik Dec 20, 2023
5cc9720
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 20, 2023
0d78d7a
adjust scheduled_change model
jurajmajerik Dec 21, 2023
bc842e4
limit query to 10,000 records
jurajmajerik Dec 21, 2023
d6b8a59
add query snapshot
jurajmajerik Dec 21, 2023
96131cd
fix conflict
jurajmajerik Dec 21, 2023
7ff2c44
use serializer to update the flag
jurajmajerik Dec 21, 2023
30e47a6
rename payload fields
jurajmajerik Dec 21, 2023
7e418ba
fix conflict
jurajmajerik Dec 21, 2023
acd5fc4
fix migrations
jurajmajerik Dec 21, 2023
f096549
add migrations manifest
jurajmajerik Dec 21, 2023
3059b90
Update query snapshots
github-actions[bot] Dec 21, 2023
049d4a2
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 21, 2023
2249402
Update query snapshots
github-actions[bot] Dec 21, 2023
5af6b0a
Update query snapshots
github-actions[bot] Dec 21, 2023
803418e
Update query snapshots
github-actions[bot] Dec 21, 2023
b4f6d2d
Update query snapshots
github-actions[bot] Dec 21, 2023
3e260ba
Update query snapshots
github-actions[bot] Dec 21, 2023
1a0f51a
Update query snapshots
github-actions[bot] Dec 21, 2023
6827222
Update query snapshots
github-actions[bot] Dec 21, 2023
4da809f
Update query snapshots
github-actions[bot] Dec 21, 2023
04a9bb5
fix migrations
jurajmajerik Dec 21, 2023
710cdb6
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 21, 2023
edac981
Update query snapshots
github-actions[bot] Dec 21, 2023
c7f1a23
Update query snapshots
github-actions[bot] Dec 21, 2023
41da321
Update query snapshots
github-actions[bot] Dec 21, 2023
9cba2f1
final adjustments
jurajmajerik Dec 21, 2023
1cde7db
fix conflict
jurajmajerik Dec 21, 2023
c77f238
Merge branch 'master' of https://github.com/PostHog/posthog into feat…
jurajmajerik Dec 21, 2023
3c524ef
add index concurrently
jurajmajerik Dec 21, 2023
1ba4020
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 21, 2023
77dc563
import AddIndexConcurrently
jurajmajerik Dec 21, 2023
7353f82
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 21, 2023
cf5862a
tweak migration
jurajmajerik Dec 21, 2023
674cc7d
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 21, 2023
4056337
tweak migration
jurajmajerik Dec 21, 2023
4f19320
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 21, 2023
846dfcf
tweak migration
jurajmajerik Dec 21, 2023
dc05e1b
Merge branch 'master' into feat/schedule-feature-flag
jurajmajerik Dec 21, 2023
62340ed
fix API test
jurajmajerik Dec 21, 2023
679052d
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 21, 2023
c1466fe
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 21, 2023
ca435c2
Merge branch 'feat/schedule-feature-flag' of https://github.com/PostH…
jurajmajerik Dec 21, 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
14 changes: 14 additions & 0 deletions posthog/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ def setup_periodic_tasks(sender: Celery, **kwargs):
name="recalculate cohorts",
)

add_periodic_task_with_expiry(
sender,
120,
process_scheduled_changes.s(),
name="process scheduled changes",
)

if clear_clickhouse_crontab := get_crontab(settings.CLEAR_CLICKHOUSE_REMOVED_DATA_SCHEDULE_CRON):
sender.add_periodic_task(
clear_clickhouse_crontab,
Expand Down Expand Up @@ -871,6 +878,13 @@ def calculate_cohort():
calculate_cohorts()


@app.task(ignore_result=True)
def process_scheduled_changes():
from posthog.tasks.process_scheduled_changes import process_scheduled_changes

process_scheduled_changes()


@app.task(ignore_result=True)
def sync_insight_cache_states_task():
from posthog.caching.insight_caching_state import sync_insight_cache_states
Expand Down
11 changes: 11 additions & 0 deletions posthog/models/feature_flag/feature_flag.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,17 @@ def get_cohort_ids(

return list(cohort_ids)

def scheduled_changes_dispatcher(self, payload):
if "field" not in payload or "value" not in payload:
raise Exception("Invalid payload")
elif payload["field"] == "active":
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
self.active = payload["value"]
elif payload["field"] == "filters":
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
for group in payload["value"]["groups"]:
self.filters["groups"].append(group)

self.save()

@property
def uses_cohorts(self) -> bool:
for condition in self.conditions:
Expand Down
2 changes: 2 additions & 0 deletions posthog/tasks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
demo_create_data,
email,
exporter,
process_scheduled_changes,
split_person,
sync_all_organization_available_features,
usage_report,
Expand All @@ -20,6 +21,7 @@
"demo_create_data",
"email",
"exporter",
"process_scheduled_changes",
"split_person",
"sync_all_organization_available_features",
"user_identify",
Expand Down
35 changes: 35 additions & 0 deletions posthog/tasks/process_scheduled_changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from posthog.models import ScheduledChange
from django.utils import timezone
from posthog.models import FeatureFlag
from django.db import transaction, OperationalError

models = {"FeatureFlag": FeatureFlag}


def process_scheduled_changes() -> None:
try:
with transaction.atomic():
scheduled_changes = ScheduledChange.objects.select_for_update(nowait=True).filter(
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
executed_at__isnull=True,
scheduled_at__lt=timezone.now(),
)

for scheduled_change in scheduled_changes.iterator(chunk_size=10000):
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
try:
# Execute the change on the model instance
model = models[scheduled_change.model_name]
instance = model.objects.get(id=scheduled_change.record_id)
instance.scheduled_changes_dispatcher(scheduled_change.payload)

# Mark scheduled change completed
scheduled_change.executed_at = timezone.now()
scheduled_change.save()

except Exception as e:
# Store the failure reason
scheduled_change.failure_reason = str(e)
scheduled_change.executed_at = timezone.now()
scheduled_change.save()
except OperationalError:
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
# Failed to obtain the lock
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
pass
91 changes: 91 additions & 0 deletions posthog/tasks/test/test_process_scheduled_changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from datetime import datetime, timedelta, timezone
from posthog.models import ScheduledChange, FeatureFlag
from posthog.test.base import APIBaseTest
from posthog.tasks.process_scheduled_changes import process_scheduled_changes


class TestProcessScheduledChanges(APIBaseTest):
jurajmajerik marked this conversation as resolved.
Show resolved Hide resolved
def test_schedule_feature_flag_set_active(self):
feature_flag = FeatureFlag.objects.create(
name="Flag 1",
key="flag-1",
active=False,
filters={"groups": [{"properties": [], "rollout_percentage": None}]},
team=self.team,
created_by=self.user,
)

ScheduledChange.objects.create(
team=self.team,
record_id=feature_flag.id,
model_name="FeatureFlag",
payload={"field": "active", "value": True},
scheduled_at=(datetime.now(timezone.utc) - timedelta(seconds=30)).isoformat(),
)

process_scheduled_changes()

updated_flag = FeatureFlag.objects.get(key="flag-1")
self.assertEqual(updated_flag.active, True)

def test_schedule_feature_flag_add_release_condition(self):
feature_flag = FeatureFlag.objects.create(
name="Flag 1",
key="flag-1",
active=False,
filters={"groups": []},
team=self.team,
created_by=self.user,
)

new_release_condition = {
"variant": None,
"properties": [{"key": "$browser", "type": "person", "value": ["Chrome"], "operator": "exact"}],
"rollout_percentage": 30,
}

payload = {
"field": "filters",
"value": {"groups": [new_release_condition], "payloads": {}, "multivariate": None},
}

ScheduledChange.objects.create(
team=self.team,
record_id=feature_flag.id,
model_name="FeatureFlag",
payload=payload,
scheduled_at=(datetime.now(timezone.utc) - timedelta(seconds=30)).isoformat(),
)

process_scheduled_changes()

updated_flag = FeatureFlag.objects.get(key="flag-1")
self.assertEqual(updated_flag.filters["groups"][0], new_release_condition)

def test_schedule_feature_flag_invalid_payload(self):
feature_flag = FeatureFlag.objects.create(
name="Flag 1",
key="flag-1",
active=False,
filters={"groups": []},
team=self.team,
created_by=self.user,
)

payload = {"foo": "bar"}

scheduled_change = ScheduledChange.objects.create(
team=self.team,
record_id=feature_flag.id,
model_name="FeatureFlag",
payload=payload,
scheduled_at=(datetime.now(timezone.utc) - timedelta(seconds=30)).isoformat(),
)

process_scheduled_changes()

updated_flag = FeatureFlag.objects.get(key="flag-1")
self.assertEqual(updated_flag.filters["groups"], [])

updated_scheduled_change = ScheduledChange.objects.get(id=scheduled_change.id)
self.assertEqual(updated_scheduled_change.failure_reason, "Invalid payload")
Loading