[WEB-1969] chore: migration for issue worklogs and issue property (#635)

* dev: add issue properties

* fix: migrations

* dev: update issue prop migrations

* dev: update issue props migration

* dev: add validation on issue properties

* chore: migration fixes

* chore: changed the migration file

* chore: minor changes

---------

Co-authored-by: pablohashescobar <[email protected]>
Co-authored-by: sriram veeraghanta <[email protected]>
This commit is contained in:
Bavisetti Narayan
2024-07-22 19:39:38 +05:30
committed by GitHub
co-authored by pablohashescobar sriram veeraghanta
parent 94faf70aca
commit 70dc14d4f6
5 changed files with 741 additions and 0 deletions
@@ -0,0 +1,534 @@
# Generated by Django 4.2.11 on 2024-07-22 13:22
from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="IssueProperty",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("name", models.CharField(max_length=255)),
("display_name", models.CharField(max_length=255)),
("description", models.TextField(blank=True, null=True)),
("logo_props", models.JSONField(blank=True, default=dict)),
("sort_order", models.FloatField(default=65535)),
(
"property_type",
models.CharField(
choices=[
("TEXT", "Text"),
("DATETIME", "Datetime"),
("DECIMAL", "Decimal"),
("BOOLEAN", "Boolean"),
("OPTION", "Option"),
("RELATION", "Relation"),
("URL", "URL"),
("EMAIL", "Email"),
("FILE", "File"),
],
max_length=255,
),
),
(
"relation_type",
models.CharField(
blank=True,
null=True,
choices=[("ISSUE", "Issue"), ("USER", "User")],
max_length=255,
),
),
("is_required", models.BooleanField(default=False)),
(
"default_value",
django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(),
blank=True,
default=list,
size=None,
),
),
("settings", models.JSONField(blank=True, default=dict)),
("is_active", models.BooleanField(default=True)),
("is_multi", models.BooleanField(default=False)),
(
"validation_rules",
models.JSONField(blank=True, default=dict),
),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"issue_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="properties",
to="db.issuetype",
),
),
(
"project",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="project_%(class)s",
to="db.project",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"workspace",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="workspace_%(class)s",
to="db.workspace",
),
),
],
options={
"db_table": "issue_properties",
"ordering": ["sort_order"],
"unique_together": {("name", "issue_type")},
},
),
migrations.CreateModel(
name="IssuePropertyOption",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("name", models.CharField(max_length=255)),
("sort_order", models.FloatField(default=65535)),
("description", models.TextField(blank=True)),
("logo_props", models.JSONField(blank=True, default=dict)),
("is_active", models.BooleanField(default=True)),
("is_default", models.BooleanField(default=False)),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"parent",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="ee.issuepropertyoption",
),
),
(
"project",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="project_%(class)s",
to="db.project",
),
),
(
"property",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="options",
to="ee.issueproperty",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"workspace",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="workspace_%(class)s",
to="db.workspace",
),
),
],
options={
"db_table": "issue_property_options",
"ordering": ["sort_order"],
"unique_together": {("name", "property")},
},
),
migrations.CreateModel(
name="IssueWorkLog",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("description", models.TextField(blank=True)),
("duration", models.IntegerField(default=0)),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="worklogs",
to="db.issue",
),
),
(
"logged_by",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="worklogs",
to=settings.AUTH_USER_MODEL,
),
),
(
"project",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="project_%(class)s",
to="db.project",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"workspace",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="workspace_%(class)s",
to="db.workspace",
),
),
],
options={
"verbose_name": "Issue Work Log",
"verbose_name_plural": "Issue Work Logs",
"db_table": "issue_worklogs",
"ordering": ("-created_at",),
},
),
migrations.CreateModel(
name="IssuePropertyValue",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("value_text", models.TextField(blank=True)),
("value_boolean", models.BooleanField(default=False)),
("value_decimal", models.FloatField(default=0)),
(
"value_datetime",
models.DateTimeField(blank=True, null=True),
),
("value_uuid", models.UUIDField(blank=True, null=True)),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="properties",
to="db.issue",
),
),
(
"project",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="project_%(class)s",
to="db.project",
),
),
(
"property",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="values",
to="ee.issueproperty",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"value_option",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="property_values",
to="ee.issuepropertyoption",
),
),
(
"workspace",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="workspace_%(class)s",
to="db.workspace",
),
),
],
options={
"db_table": "issue_property_values",
"ordering": ["-created_at"],
},
),
migrations.CreateModel(
name="IssuePropertyActivity",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("old_value", models.TextField(blank=True)),
("new_value", models.TextField(blank=True)),
("old_identifier", models.UUIDField(blank=True, null=True)),
("new_identifier", models.UUIDField(blank=True, null=True)),
("action", models.CharField(max_length=255)),
("epoch", models.FloatField(null=True)),
(
"comment",
models.TextField(blank=True, verbose_name="Comment"),
),
(
"actor",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="issue_property_activities",
to=settings.AUTH_USER_MODEL,
),
),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="activities",
to="db.issue",
),
),
(
"project",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="project_%(class)s",
to="db.project",
),
),
(
"property",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="activities",
to="ee.issueproperty",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"workspace",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="workspace_%(class)s",
to="db.workspace",
),
),
],
options={
"db_table": "issue_property_activities",
"ordering": ["-created_at"],
},
),
]
+7
View File
@@ -0,0 +1,7 @@
from .issue_properties import (
IssueProperty,
IssuePropertyOption,
IssuePropertyValue,
IssuePropertyActivity,
)
from .issue import IssueWorkLog
+28
View File
@@ -0,0 +1,28 @@
# Django imports
from django.db import models
from django.conf import settings
# Module imports
from plane.db.models import ProjectBaseModel, Issue
class IssueWorkLog(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="worklogs"
)
description = models.TextField(blank=True)
logged_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="worklogs",
)
duration = models.IntegerField(default=0)
class Meta:
verbose_name = "Issue Work Log"
verbose_name_plural = "Issue Work Logs"
db_table = "issue_worklogs"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.logged_by.email}"
@@ -0,0 +1,172 @@
# Django imports
from django.db import models
from django.conf import settings
from django.template.defaultfilters import slugify
from django.contrib.postgres.fields import ArrayField
# Module imports
from plane.db.models import WorkspaceBaseModel
class PropertyTypeEnum(models.TextChoices):
TEXT = "TEXT", "Text"
DATETIME = "DATETIME", "Datetime"
DECIMAL = "DECIMAL", "Decimal"
BOOLEAN = "BOOLEAN", "Boolean"
OPTION = "OPTION", "Option"
RELATION = "RELATION", "Relation"
URL = "URL", "URL"
EMAIL = "EMAIL", "Email"
FILE = "FILE", "File"
class RelationTypeEnum(models.TextChoices):
ISSUE = "ISSUE", "Issue"
USER = "USER", "User"
class IssueProperty(WorkspaceBaseModel):
name = models.CharField(max_length=255)
display_name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
logo_props = models.JSONField(blank=True, default=dict)
sort_order = models.FloatField(default=65535)
property_type = models.CharField(
max_length=255,
choices=PropertyTypeEnum.choices,
)
relation_type = models.CharField(
max_length=255,
blank=True,
null=True,
choices=RelationTypeEnum.choices,
)
is_required = models.BooleanField(default=False)
default_value = ArrayField(models.TextField(), blank=True, default=list)
settings = models.JSONField(blank=True, default=dict)
is_active = models.BooleanField(default=True)
issue_type = models.ForeignKey(
"db.IssueType", on_delete=models.CASCADE, related_name="properties"
)
is_multi = models.BooleanField(default=False)
validation_rules = models.JSONField(blank=True, default=dict)
class Meta:
ordering = ["sort_order"]
unique_together = ["name", "issue_type"]
db_table = "issue_properties"
def save(self, *args, **kwargs):
self.name = slugify(self.display_name)
if self._state.adding:
# Get the maximum sequence value from the database
last_id = IssueProperty.objects.filter(
project=self.project
).aggregate(largest=models.Max("sort_order"))["largest"]
# if last_id is not None
if last_id is not None:
self.sort_order = last_id + 10000
super(IssueProperty, self).save(*args, **kwargs)
def __str__(self):
return self.display_name
class IssuePropertyOption(WorkspaceBaseModel):
name = models.CharField(max_length=255)
sort_order = models.FloatField(default=65535)
property = models.ForeignKey(
IssueProperty, on_delete=models.CASCADE, related_name="options"
)
description = models.TextField(blank=True)
logo_props = models.JSONField(blank=True, default=dict)
is_active = models.BooleanField(default=True)
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
related_name="children",
null=True,
blank=True,
)
is_default = models.BooleanField(default=False)
class Meta:
ordering = ["sort_order"]
unique_together = ["name", "property"]
db_table = "issue_property_options"
def save(self, *args, **kwargs):
if self._state.adding:
# Get the maximum sequence value from the database
last_id = IssuePropertyOption.objects.filter(
project=self.project
).aggregate(largest=models.Max("sort_order"))["largest"]
# if last_id is not None
if last_id is not None:
self.sort_order = last_id + 10000
if self.is_default:
self.is_active = True
super(IssuePropertyOption, self).save(*args, **kwargs)
class IssuePropertyValue(WorkspaceBaseModel):
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="properties"
)
property = models.ForeignKey(
IssueProperty, on_delete=models.CASCADE, related_name="values"
)
value_text = models.TextField(blank=True)
value_boolean = models.BooleanField(default=False)
value_decimal = models.FloatField(default=0)
value_datetime = models.DateTimeField(blank=True, null=True)
value_uuid = models.UUIDField(blank=True, null=True)
value_option = models.ForeignKey(
IssuePropertyOption,
on_delete=models.CASCADE,
related_name="property_values",
blank=True,
null=True,
)
class Meta:
ordering = ["-created_at"]
db_table = "issue_property_values"
def __str__(self):
return f"{self.property.display_name}"
class IssuePropertyActivity(WorkspaceBaseModel):
old_value = models.TextField(blank=True)
new_value = models.TextField(blank=True)
old_identifier = models.UUIDField(blank=True, null=True)
new_identifier = models.UUIDField(blank=True, null=True)
property = models.ForeignKey(
IssueProperty, on_delete=models.CASCADE, related_name="activities"
)
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="activities"
)
actor = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name="issue_property_activities",
)
action = models.CharField(max_length=255)
epoch = models.FloatField(null=True)
comment = models.TextField(verbose_name="Comment", blank=True)
class Meta:
ordering = ["-created_at"]
db_table = "issue_property_activities"
def __str__(self):
return f"{self.property.display_name} - {self.issue_id}"