From 64328f6f53c71abf30d49356efa51c95f9207ee1 Mon Sep 17 00:00:00 2001 From: sangeethailango Date: Mon, 15 Dec 2025 15:44:13 +0530 Subject: [PATCH 1/8] migration: added version field in webhook --- .../db/migrations/0113_webhook_version.py | 18 ++++++++++++++++++ apps/api/plane/db/models/webhook.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 apps/api/plane/db/migrations/0113_webhook_version.py diff --git a/apps/api/plane/db/migrations/0113_webhook_version.py b/apps/api/plane/db/migrations/0113_webhook_version.py new file mode 100644 index 00000000000..77a0411ef1a --- /dev/null +++ b/apps/api/plane/db/migrations/0113_webhook_version.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.26 on 2025-12-15 10:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0112_auto_20251124_0603'), + ] + + operations = [ + migrations.AddField( + model_name='webhook', + name='version', + field=models.CharField(default='v1'), + ), + ] diff --git a/apps/api/plane/db/models/webhook.py b/apps/api/plane/db/models/webhook.py index 8872d0bb235..e246f2452e9 100644 --- a/apps/api/plane/db/models/webhook.py +++ b/apps/api/plane/db/models/webhook.py @@ -38,6 +38,7 @@ class Webhook(BaseModel): cycle = models.BooleanField(default=False) issue_comment = models.BooleanField(default=False) is_internal = models.BooleanField(default=False) + version = models.CharField(default="v1") def __str__(self): return f"{self.workspace.slug} {self.url}" From 04ec99f71186dfec3c49f754814995054c713e53 Mon Sep 17 00:00:00 2001 From: sangeethailango Date: Mon, 15 Dec 2025 15:59:51 +0530 Subject: [PATCH 2/8] chore: add max_length --- apps/api/plane/db/migrations/0113_webhook_version.py | 4 ++-- apps/api/plane/db/models/webhook.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/plane/db/migrations/0113_webhook_version.py b/apps/api/plane/db/migrations/0113_webhook_version.py index 77a0411ef1a..e4571b78ba7 100644 --- a/apps/api/plane/db/migrations/0113_webhook_version.py +++ b/apps/api/plane/db/migrations/0113_webhook_version.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.26 on 2025-12-15 10:12 +# Generated by Django 4.2.26 on 2025-12-15 10:29 from django.db import migrations, models @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='webhook', name='version', - field=models.CharField(default='v1'), + field=models.CharField(default='v1', max_length=50), ), ] diff --git a/apps/api/plane/db/models/webhook.py b/apps/api/plane/db/models/webhook.py index e246f2452e9..298b0dba3b0 100644 --- a/apps/api/plane/db/models/webhook.py +++ b/apps/api/plane/db/models/webhook.py @@ -38,7 +38,7 @@ class Webhook(BaseModel): cycle = models.BooleanField(default=False) issue_comment = models.BooleanField(default=False) is_internal = models.BooleanField(default=False) - version = models.CharField(default="v1") + version = models.CharField(default="v1", max_length=50) def __str__(self): return f"{self.workspace.slug} {self.url}" From ab47d4cd63e84733d2424531172a3b5139a2ec2e Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 2 Dec 2025 20:33:07 +0530 Subject: [PATCH 3/8] chore: added product tour fields --- ...e_is_navigation_tour_completed_and_more.py | 42 +++++++++++++++++++ apps/api/plane/db/models/user.py | 3 ++ apps/api/plane/db/models/workspace.py | 11 +++++ 3 files changed, 56 insertions(+) create mode 100644 apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py diff --git a/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py b/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py new file mode 100644 index 00000000000..0c09403225b --- /dev/null +++ b/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.25 on 2025-12-02 14:16 + +from django.db import migrations, models +import plane.db.models.workspace + + +def get_default_feature_tours(): + return { + "work_items": True, + "cycles": True, + "modules": True, + "intake": True, + "pages": True, + } + +def populate_feature_tours(apps, _schema_editor): + WorkspaceUserProperties = apps.get_model('db', 'WorkspaceUserProperties') + default_value = get_default_feature_tours() + # Use bulk update for better performance + WorkspaceUserProperties.objects.all().update(feature_tours=default_value) + + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0112_auto_20251124_0603'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='is_navigation_tour_completed', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='workspaceuserproperties', + name='feature_tours', + field=models.JSONField(default=plane.db.models.workspace.get_default_feature_tours), + ), + migrations.RunPython(populate_feature_tours, reverse_code=migrations.RunPython.noop), + ] \ No newline at end of file diff --git a/apps/api/plane/db/models/user.py b/apps/api/plane/db/models/user.py index ee70032cf42..9b5f5ac6b55 100644 --- a/apps/api/plane/db/models/user.py +++ b/apps/api/plane/db/models/user.py @@ -218,6 +218,9 @@ class Profile(TimeAuditModel): goals = models.JSONField(default=dict) background_color = models.CharField(max_length=255, default=get_random_color) + # navigation tour + is_navigation_tour_completed = models.BooleanField(default=False) + # marketing has_marketing_email_consent = models.BooleanField(default=False) diff --git a/apps/api/plane/db/models/workspace.py b/apps/api/plane/db/models/workspace.py index d3470d531ea..4f34caeb729 100644 --- a/apps/api/plane/db/models/workspace.py +++ b/apps/api/plane/db/models/workspace.py @@ -112,6 +112,16 @@ def slug_validator(value): raise ValidationError("Slug is not valid") +def get_default_feature_tours(): + return { + "work_items": False, + "cycles": False, + "modules": False, + "intake": False, + "pages": False, + } + + class Workspace(BaseModel): TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones)) @@ -325,6 +335,7 @@ class NavigationControlPreference(models.TextChoices): choices=NavigationControlPreference.choices, default=NavigationControlPreference.ACCORDION, ) + feature_tours = models.JSONField(default=get_default_feature_tours) class Meta: unique_together = ["workspace", "user", "deleted_at"] From 1d9390d4cabbfebc83a7b90d6506a0664f839a23 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 23 Dec 2025 18:52:05 +0530 Subject: [PATCH 4/8] chore: updated the migration file --- ...e_is_navigation_tour_completed_and_more.py | 28 ++---------------- .../db/migrations/0113_webhook_version.py | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py b/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py index 0c09403225b..4c6d0fd2d51 100644 --- a/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py +++ b/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py @@ -1,23 +1,9 @@ # Generated by Django 4.2.25 on 2025-12-02 14:16 -from django.db import migrations, models -import plane.db.models.workspace -def get_default_feature_tours(): - return { - "work_items": True, - "cycles": True, - "modules": True, - "intake": True, - "pages": True, - } -def populate_feature_tours(apps, _schema_editor): - WorkspaceUserProperties = apps.get_model('db', 'WorkspaceUserProperties') - default_value = get_default_feature_tours() - # Use bulk update for better performance - WorkspaceUserProperties.objects.all().update(feature_tours=default_value) + @@ -28,15 +14,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='profile', - name='is_navigation_tour_completed', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='workspaceuserproperties', - name='feature_tours', - field=models.JSONField(default=plane.db.models.workspace.get_default_feature_tours), - ), - migrations.RunPython(populate_feature_tours, reverse_code=migrations.RunPython.noop), + ] \ No newline at end of file diff --git a/apps/api/plane/db/migrations/0113_webhook_version.py b/apps/api/plane/db/migrations/0113_webhook_version.py index e4571b78ba7..3beb9918731 100644 --- a/apps/api/plane/db/migrations/0113_webhook_version.py +++ b/apps/api/plane/db/migrations/0113_webhook_version.py @@ -1,6 +1,24 @@ # Generated by Django 4.2.26 on 2025-12-15 10:29 from django.db import migrations, models +import plane.db.models.workspace + + +def get_default_feature_tours(): + return { + "work_items": True, + "cycles": True, + "modules": True, + "intake": True, + "pages": True, + } + + +def populate_feature_tours(apps, _schema_editor): + WorkspaceUserProperties = apps.get_model('db', 'WorkspaceUserProperties') + default_value = get_default_feature_tours() + # Use bulk update for better performance + WorkspaceUserProperties.objects.all().update(feature_tours=default_value) class Migration(migrations.Migration): @@ -15,4 +33,15 @@ class Migration(migrations.Migration): name='version', field=models.CharField(default='v1', max_length=50), ), + migrations.AddField( + model_name='profile', + name='is_navigation_tour_completed', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='workspaceuserproperties', + name='feature_tours', + field=models.JSONField(default=plane.db.models.workspace.get_default_feature_tours), + ), + migrations.RunPython(populate_feature_tours, reverse_code=migrations.RunPython.noop), ] From e76d3688da9286f77ce2b8d235a831d647f24ac0 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 23 Dec 2025 18:53:01 +0530 Subject: [PATCH 5/8] chore: removed the duplicated migration file --- ...le_is_navigation_tour_completed_and_more.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py diff --git a/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py b/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py deleted file mode 100644 index 4c6d0fd2d51..00000000000 --- a/apps/api/plane/db/migrations/0113_profile_is_navigation_tour_completed_and_more.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.2.25 on 2025-12-02 14:16 - - - - - - - - -class Migration(migrations.Migration): - - dependencies = [ - ('db', '0112_auto_20251124_0603'), - ] - - operations = [ - - ] \ No newline at end of file From 56a41a8d674ce7abf40c470477c0d6b1a859e0be Mon Sep 17 00:00:00 2001 From: sangeethailango Date: Tue, 23 Dec 2025 19:20:34 +0530 Subject: [PATCH 6/8] chore: added allowed_rate_limit for api_tokens --- apps/api/plane/db/migrations/0113_webhook_version.py | 5 +++++ apps/api/plane/db/models/api.py | 1 + 2 files changed, 6 insertions(+) diff --git a/apps/api/plane/db/migrations/0113_webhook_version.py b/apps/api/plane/db/migrations/0113_webhook_version.py index 3beb9918731..256a5fe193b 100644 --- a/apps/api/plane/db/migrations/0113_webhook_version.py +++ b/apps/api/plane/db/migrations/0113_webhook_version.py @@ -42,6 +42,11 @@ class Migration(migrations.Migration): model_name='workspaceuserproperties', name='feature_tours', field=models.JSONField(default=plane.db.models.workspace.get_default_feature_tours), + ), + migrations.AddField( + model_name='apitoken', + name='allowed_rate_limit', + field=models.CharField(default='60/min', max_length=255), ), migrations.RunPython(populate_feature_tours, reverse_code=migrations.RunPython.noop), ] diff --git a/apps/api/plane/db/models/api.py b/apps/api/plane/db/models/api.py index 7d040ebc284..75449a74283 100644 --- a/apps/api/plane/db/models/api.py +++ b/apps/api/plane/db/models/api.py @@ -32,6 +32,7 @@ class APIToken(BaseModel): workspace = models.ForeignKey("db.Workspace", related_name="api_tokens", on_delete=models.CASCADE, null=True) expired_at = models.DateTimeField(blank=True, null=True) is_service = models.BooleanField(default=False) + allowed_rate_limit = models.CharField(max_length=255, default="60/min") class Meta: verbose_name = "API Token" From 9cc3f811ff00f545be53830566a2cd13d0d67405 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Wed, 24 Dec 2025 17:45:02 +0530 Subject: [PATCH 7/8] chore: changed key feature tour to product tour --- .../plane/db/migrations/0113_webhook_version.py | 14 +++++++------- apps/api/plane/db/models/workspace.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/api/plane/db/migrations/0113_webhook_version.py b/apps/api/plane/db/migrations/0113_webhook_version.py index 3beb9918731..f55bb1f49b6 100644 --- a/apps/api/plane/db/migrations/0113_webhook_version.py +++ b/apps/api/plane/db/migrations/0113_webhook_version.py @@ -4,7 +4,7 @@ import plane.db.models.workspace -def get_default_feature_tours(): +def get_default_product_tour(): return { "work_items": True, "cycles": True, @@ -14,11 +14,11 @@ def get_default_feature_tours(): } -def populate_feature_tours(apps, _schema_editor): +def populate_product_tour(apps, _schema_editor): WorkspaceUserProperties = apps.get_model('db', 'WorkspaceUserProperties') - default_value = get_default_feature_tours() + default_value = get_default_product_tour() # Use bulk update for better performance - WorkspaceUserProperties.objects.all().update(feature_tours=default_value) + WorkspaceUserProperties.objects.all().update(product_tour=default_value) class Migration(migrations.Migration): @@ -40,8 +40,8 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='workspaceuserproperties', - name='feature_tours', - field=models.JSONField(default=plane.db.models.workspace.get_default_feature_tours), + name='product_tour', + field=models.JSONField(default=plane.db.models.workspace.get_default_product_tour), ), - migrations.RunPython(populate_feature_tours, reverse_code=migrations.RunPython.noop), + migrations.RunPython(populate_product_tour, reverse_code=migrations.RunPython.noop), ] diff --git a/apps/api/plane/db/models/workspace.py b/apps/api/plane/db/models/workspace.py index 4f34caeb729..9690168a11a 100644 --- a/apps/api/plane/db/models/workspace.py +++ b/apps/api/plane/db/models/workspace.py @@ -112,7 +112,7 @@ def slug_validator(value): raise ValidationError("Slug is not valid") -def get_default_feature_tours(): +def get_default_product_tour(): return { "work_items": False, "cycles": False, @@ -335,7 +335,7 @@ class NavigationControlPreference(models.TextChoices): choices=NavigationControlPreference.choices, default=NavigationControlPreference.ACCORDION, ) - feature_tours = models.JSONField(default=get_default_feature_tours) + product_tour = models.JSONField(default=get_default_product_tour) class Meta: unique_together = ["workspace", "user", "deleted_at"] From a2fcc06d96dd4890c49a14edd74da4906184edaf Mon Sep 17 00:00:00 2001 From: sangeethailango Date: Mon, 29 Dec 2025 17:52:38 +0530 Subject: [PATCH 8/8] chore: added is_subscribed_to_changelog field --- apps/api/plane/db/migrations/0113_webhook_version.py | 5 +++++ apps/api/plane/db/models/user.py | 1 + 2 files changed, 6 insertions(+) diff --git a/apps/api/plane/db/migrations/0113_webhook_version.py b/apps/api/plane/db/migrations/0113_webhook_version.py index 229174430fd..3be3120eb43 100644 --- a/apps/api/plane/db/migrations/0113_webhook_version.py +++ b/apps/api/plane/db/migrations/0113_webhook_version.py @@ -47,6 +47,11 @@ class Migration(migrations.Migration): model_name='apitoken', name='allowed_rate_limit', field=models.CharField(default='60/min', max_length=255), + ), + migrations.AddField( + model_name='profile', + name='is_subscribed_to_changelog', + field=models.BooleanField(default=False), ), migrations.RunPython(populate_product_tour, reverse_code=migrations.RunPython.noop), ] diff --git a/apps/api/plane/db/models/user.py b/apps/api/plane/db/models/user.py index 2a4f42f8fcc..b0f571be9c8 100644 --- a/apps/api/plane/db/models/user.py +++ b/apps/api/plane/db/models/user.py @@ -238,6 +238,7 @@ class Profile(TimeAuditModel): # marketing has_marketing_email_consent = models.BooleanField(default=False) + is_subscribed_to_changelog = models.BooleanField(default=False) class Meta: verbose_name = "Profile"