[WEB-5845] chore: changing description field to description json (#8230)

* chore: migrating description to description json

* chore: replace description with description_json

* chore: updated migration file

* chore: updated the migration file

* chore: added description key in external endpoint

* chore: updated the migration file

* chore: updated the typo

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
Bavisetti Narayan 2026-01-22 18:23:59 +05:30 committed by GitHub
parent 6c8779c8d3
commit 2a29ab8d4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 85 additions and 54 deletions

View file

@ -13,11 +13,14 @@ class IssueForIntakeSerializer(BaseSerializer):
content validation and priority assignment for triage workflows. content validation and priority assignment for triage workflows.
""" """
description = serializers.JSONField(source="description_json", required=False, allow_null=True)
class Meta: class Meta:
model = Issue model = Issue
fields = [ fields = [
"name", "name",
"description", "description", # Deprecated
"description_json",
"description_html", "description_html",
"priority", "priority",
] ]

View file

@ -65,7 +65,7 @@ class IssueSerializer(BaseSerializer):
class Meta: class Meta:
model = Issue model = Issue
read_only_fields = ["id", "workspace", "project", "updated_by", "updated_at"] read_only_fields = ["id", "workspace", "project", "updated_by", "updated_at"]
exclude = ["description", "description_stripped"] exclude = ["description_json", "description_stripped"]
def validate(self, data): def validate(self, data):
if ( if (
@ -633,6 +633,7 @@ class IssueExpandSerializer(BaseSerializer):
labels = serializers.SerializerMethodField() labels = serializers.SerializerMethodField()
assignees = serializers.SerializerMethodField() assignees = serializers.SerializerMethodField()
state = StateLiteSerializer(read_only=True) state = StateLiteSerializer(read_only=True)
description = serializers.JSONField(source="description_json", read_only=True)
def get_labels(self, obj): def get_labels(self, obj):
expand = self.context.get("expand", []) expand = self.context.get("expand", [])

View file

@ -180,11 +180,14 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView):
) )
# create an issue # create an issue
issue_data = request.data.get("issue", {})
# Accept both "description" and "description_json" keys for the description_json field
description_json = issue_data.get("description") or issue_data.get("description_json") or {}
issue = Issue.objects.create( issue = Issue.objects.create(
name=request.data.get("issue", {}).get("name"), name=issue_data.get("name"),
description=request.data.get("issue", {}).get("description", {}), description_json=description_json,
description_html=request.data.get("issue", {}).get("description_html", "<p></p>"), description_html=issue_data.get("description_html", "<p></p>"),
priority=request.data.get("issue", {}).get("priority", "none"), priority=issue_data.get("priority", "none"),
project_id=project_id, project_id=project_id,
state_id=triage_state.id, state_id=triage_state.id,
) )
@ -365,10 +368,11 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView):
# Only allow guests to edit name and description # Only allow guests to edit name and description
if project_member.role <= 5: if project_member.role <= 5:
description_json = issue_data.get("description") or issue_data.get("description_json") or {}
issue_data = { issue_data = {
"name": issue_data.get("name", issue.name), "name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html), "description_html": issue_data.get("description_html", issue.description_html),
"description": issue_data.get("description", issue.description), "description_json": description_json,
} }
issue_serializer = IssueSerializer(issue, data=issue_data, partial=True) issue_serializer = IssueSerializer(issue, data=issue_data, partial=True)

View file

@ -53,7 +53,7 @@ class IssueFlatSerializer(BaseSerializer):
fields = [ fields = [
"id", "id",
"name", "name",
"description", "description_json",
"description_html", "description_html",
"priority", "priority",
"start_date", "start_date",

View file

@ -58,7 +58,7 @@ class PageSerializer(BaseSerializer):
labels = validated_data.pop("labels", None) labels = validated_data.pop("labels", None)
project_id = self.context["project_id"] project_id = self.context["project_id"]
owned_by_id = self.context["owned_by_id"] owned_by_id = self.context["owned_by_id"]
description = self.context["description"] description_json = self.context["description_json"]
description_binary = self.context["description_binary"] description_binary = self.context["description_binary"]
description_html = self.context["description_html"] description_html = self.context["description_html"]
@ -68,7 +68,7 @@ class PageSerializer(BaseSerializer):
# Create the page # Create the page
page = Page.objects.create( page = Page.objects.create(
**validated_data, **validated_data,
description=description, description_json=description_json,
description_binary=description_binary, description_binary=description_binary,
description_html=description_html, description_html=description_html,
owned_by_id=owned_by_id, owned_by_id=owned_by_id,
@ -171,7 +171,7 @@ class PageBinaryUpdateSerializer(serializers.Serializer):
description_binary = serializers.CharField(required=False, allow_blank=True) description_binary = serializers.CharField(required=False, allow_blank=True)
description_html = serializers.CharField(required=False, allow_blank=True) description_html = serializers.CharField(required=False, allow_blank=True)
description = serializers.JSONField(required=False, allow_null=True) description_json = serializers.JSONField(required=False, allow_null=True)
def validate_description_binary(self, value): def validate_description_binary(self, value):
"""Validate the base64-encoded binary data""" """Validate the base64-encoded binary data"""
@ -214,8 +214,8 @@ class PageBinaryUpdateSerializer(serializers.Serializer):
if "description_html" in validated_data: if "description_html" in validated_data:
instance.description_html = validated_data.get("description_html") instance.description_html = validated_data.get("description_html")
if "description" in validated_data: if "description_json" in validated_data:
instance.description = validated_data.get("description") instance.description_json = validated_data.get("description_json")
instance.save() instance.save()
return instance return instance

View file

@ -394,7 +394,7 @@ class IntakeIssueViewSet(BaseViewSet):
issue_data = { issue_data = {
"name": issue_data.get("name", issue.name), "name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html), "description_html": issue_data.get("description_html", issue.description_html),
"description": issue_data.get("description", issue.description), "description_json": issue_data.get("description_json", issue.description_json),
} }
issue_current_instance = json.dumps(IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder) issue_current_instance = json.dumps(IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder)

View file

@ -128,7 +128,7 @@ class PageViewSet(BaseViewSet):
context={ context={
"project_id": project_id, "project_id": project_id,
"owned_by_id": request.user.id, "owned_by_id": request.user.id,
"description": request.data.get("description", {}), "description_json": request.data.get("description_json", {}),
"description_binary": request.data.get("description_binary", None), "description_binary": request.data.get("description_binary", None),
"description_html": request.data.get("description_html", "<p></p>"), "description_html": request.data.get("description_html", "<p></p>"),
}, },

View file

@ -141,7 +141,7 @@ def copy_s3_objects_of_description_and_assets(entity_name, entity_identifier, pr
external_data = sync_with_external_service(entity_name, updated_html) external_data = sync_with_external_service(entity_name, updated_html)
if external_data: if external_data:
entity.description = external_data.get("description") entity.description_json = external_data.get("description_json")
entity.description_binary = base64.b64decode(external_data.get("description_binary")) entity.description_binary = base64.b64decode(external_data.get("description_binary"))
entity.save() entity.save()

View file

@ -59,7 +59,7 @@ def sync_issue_description_version(batch_size=5000, offset=0, countdown=300):
"description_binary", "description_binary",
"description_html", "description_html",
"description_stripped", "description_stripped",
"description", "description_json",
)[offset:end_offset] )[offset:end_offset]
) )
@ -92,7 +92,7 @@ def sync_issue_description_version(batch_size=5000, offset=0, countdown=300):
description_binary=issue.description_binary, description_binary=issue.description_binary,
description_html=issue.description_html, description_html=issue.description_html,
description_stripped=issue.description_stripped, description_stripped=issue.description_stripped,
description_json=issue.description, description_json=issue.description_json,
) )
) )

View file

@ -19,7 +19,7 @@ def should_update_existing_version(
def update_existing_version(version: IssueDescriptionVersion, issue) -> None: def update_existing_version(version: IssueDescriptionVersion, issue) -> None:
version.description_json = issue.description version.description_json = issue.description_json
version.description_html = issue.description_html version.description_html = issue.description_html
version.description_binary = issue.description_binary version.description_binary = issue.description_binary
version.description_stripped = issue.description_stripped version.description_stripped = issue.description_stripped

View file

@ -28,7 +28,7 @@ def page_version(page_id, existing_instance, user_id):
description_binary=page.description_binary, description_binary=page.description_binary,
owned_by_id=user_id, owned_by_id=user_id,
last_saved_at=page.updated_at, last_saved_at=page.updated_at,
description_json=page.description, description_json=page.description_json,
description_stripped=page.description_stripped, description_stripped=page.description_stripped,
) )

View file

@ -359,7 +359,7 @@ def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_us
is_global=False, is_global=False,
access=page_seed.get("access", Page.PUBLIC_ACCESS), access=page_seed.get("access", Page.PUBLIC_ACCESS),
name=page_seed.get("name"), name=page_seed.get("name"),
description=page_seed.get("description", {}), description_json=page_seed.get("description_json", {}),
description_html=page_seed.get("description_html", "<p></p>"), description_html=page_seed.get("description_html", "<p></p>"),
description_binary=page_seed.get("description_binary", None), description_binary=page_seed.get("description_binary", None),
description_stripped=page_seed.get("description_stripped", None), description_stripped=page_seed.get("description_stripped", None),

View file

@ -0,0 +1,28 @@
# Generated by Django 4.2.22 on 2026-01-15 09:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('db', '0116_workspacemember_explored_features_and_more'),
]
operations = [
migrations.RenameField(
model_name='draftissue',
old_name='description',
new_name='description_json',
),
migrations.RenameField(
model_name='issue',
old_name='description',
new_name='description_json',
),
migrations.RenameField(
model_name='page',
old_name='description',
new_name='description_json',
),
]

View file

@ -39,7 +39,7 @@ class DraftIssue(WorkspaceBaseModel):
blank=True, blank=True,
) )
name = models.CharField(max_length=255, verbose_name="Issue Name", blank=True, null=True) name = models.CharField(max_length=255, verbose_name="Issue Name", blank=True, null=True)
description = models.JSONField(blank=True, default=dict) description_json = models.JSONField(blank=True, default=dict)
description_html = models.TextField(blank=True, default="<p></p>") description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True) description_stripped = models.TextField(blank=True, null=True)
description_binary = models.BinaryField(null=True) description_binary = models.BinaryField(null=True)

View file

@ -128,7 +128,7 @@ class Issue(ProjectBaseModel):
blank=True, blank=True,
) )
name = models.CharField(max_length=255, verbose_name="Issue Name") name = models.CharField(max_length=255, verbose_name="Issue Name")
description = models.JSONField(blank=True, default=dict) description_json = models.JSONField(blank=True, default=dict)
description_html = models.TextField(blank=True, default="<p></p>") description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True) description_stripped = models.TextField(blank=True, null=True)
description_binary = models.BinaryField(null=True) description_binary = models.BinaryField(null=True)
@ -800,7 +800,7 @@ class IssueDescriptionVersion(ProjectBaseModel):
description_binary=issue.description_binary, description_binary=issue.description_binary,
description_html=issue.description_html, description_html=issue.description_html,
description_stripped=issue.description_stripped, description_stripped=issue.description_stripped,
description_json=issue.description, description_json=issue.description_json,
) )
return True return True
except Exception as e: except Exception as e:

View file

@ -25,7 +25,7 @@ class Page(BaseModel):
workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="pages") workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="pages")
name = models.TextField(blank=True) name = models.TextField(blank=True)
description = models.JSONField(default=dict, blank=True) description_json = models.JSONField(default=dict, blank=True)
description_binary = models.BinaryField(null=True) description_binary = models.BinaryField(null=True)
description_html = models.TextField(blank=True, default="<p></p>") description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True) description_stripped = models.TextField(blank=True, null=True)

View file

@ -193,7 +193,7 @@ class IssueFlatSerializer(BaseSerializer):
fields = [ fields = [
"id", "id",
"name", "name",
"description", "description_json",
"description_html", "description_html",
"priority", "priority",
"start_date", "start_date",

View file

@ -140,7 +140,7 @@ class IntakeIssuePublicViewSet(BaseViewSet):
# create an issue # create an issue
issue = Issue.objects.create( issue = Issue.objects.create(
name=request.data.get("issue", {}).get("name"), name=request.data.get("issue", {}).get("name"),
description=request.data.get("issue", {}).get("description", {}), description_json=request.data.get("issue", {}).get("description_json", {}),
description_html=request.data.get("issue", {}).get("description_html", "<p></p>"), description_html=request.data.get("issue", {}).get("description_html", "<p></p>"),
priority=request.data.get("issue", {}).get("priority", "low"), priority=request.data.get("issue", {}).get("priority", "low"),
project_id=project_deploy_board.project_id, project_id=project_deploy_board.project_id,
@ -201,7 +201,7 @@ class IntakeIssuePublicViewSet(BaseViewSet):
issue_data = { issue_data = {
"name": issue_data.get("name", issue.name), "name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html), "description_html": issue_data.get("description_html", issue.description_html),
"description": issue_data.get("description", issue.description), "description_json": issue_data.get("description_json", issue.description_json),
} }
issue_serializer = IssueCreateSerializer( issue_serializer = IssueCreateSerializer(

View file

@ -744,7 +744,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
"name", "name",
"state_id", "state_id",
"sort_order", "sort_order",
"description", "description_json",
"description_html", "description_html",
"description_stripped", "description_stripped",
"description_binary", "description_binary",

View file

@ -27,14 +27,14 @@ export class DocumentController {
const { description_html, variant } = validatedData; const { description_html, variant } = validatedData;
// Process document conversion // Process document conversion
const { description, description_binary } = convertHTMLDocumentToAllFormats({ const { description_json, description_binary } = convertHTMLDocumentToAllFormats({
document_html: description_html, document_html: description_html,
variant, variant,
}); });
// Return successful response // Return successful response
res.status(200).json({ res.status(200).json({
description, description_json,
description_binary, description_binary,
}); });
} catch (error) { } catch (error) {

View file

@ -1,11 +1,12 @@
import { Database as HocuspocusDatabase } from "@hocuspocus/extension-database"; import { Database as HocuspocusDatabase } from "@hocuspocus/extension-database";
// utils // plane imports
import { import {
getAllDocumentFormatsFromDocumentEditorBinaryData, getAllDocumentFormatsFromDocumentEditorBinaryData,
getBinaryDataFromDocumentEditorHTMLString, getBinaryDataFromDocumentEditorHTMLString,
} from "@plane/editor"; } from "@plane/editor";
// logger import type { TDocumentPayload } from "@plane/types";
import { logger } from "@plane/logger"; import { logger } from "@plane/logger";
// lib
import { AppError } from "@/lib/errors"; import { AppError } from "@/lib/errors";
// services // services
import { getPageService } from "@/services/page/handler"; import { getPageService } from "@/services/page/handler";
@ -36,10 +37,10 @@ const fetchDocument = async ({ context, documentName: pageId, instance }: FetchP
convertedBinaryData, convertedBinaryData,
true true
); );
const payload = { const payload: TDocumentPayload = {
description_binary: contentBinaryEncoded, description_binary: contentBinaryEncoded,
description_html: contentHTML, description_html: contentHTML,
description: contentJSON, description_json: contentJSON,
}; };
await service.updateDescriptionBinary(pageId, payload); await service.updateDescriptionBinary(pageId, payload);
} catch (e) { } catch (e) {
@ -76,10 +77,10 @@ const storeDocument = async ({
true true
); );
// create payload // create payload
const payload = { const payload: TDocumentPayload = {
description_binary: contentBinaryEncoded, description_binary: contentBinaryEncoded,
description_html: contentHTML, description_html: contentHTML,
description: contentJSON, description_json: contentJSON,
}; };
await service.updateDescriptionBinary(pageId, payload); await service.updateDescriptionBinary(pageId, payload);
} catch (error) { } catch (error) {

View file

@ -1,15 +1,9 @@
import { logger } from "@plane/logger"; import { logger } from "@plane/logger";
import type { TPage } from "@plane/types"; import type { TDocumentPayload, TPage } from "@plane/types";
// services // services
import { AppError } from "@/lib/errors"; import { AppError } from "@/lib/errors";
import { APIService } from "../api.service"; import { APIService } from "../api.service";
export type TPageDescriptionPayload = {
description_binary: string;
description_html: string;
description: object;
};
export abstract class PageCoreService extends APIService { export abstract class PageCoreService extends APIService {
protected abstract basePath: string; protected abstract basePath: string;
@ -103,7 +97,7 @@ export abstract class PageCoreService extends APIService {
} }
} }
async updateDescriptionBinary(pageId: string, data: TPageDescriptionPayload): Promise<any> { async updateDescriptionBinary(pageId: string, data: TDocumentPayload): Promise<any> {
return this.patch(`${this.basePath}/pages/${pageId}/description/`, data, { return this.patch(`${this.basePath}/pages/${pageId}/description/`, data, {
headers: this.getHeader(), headers: this.getHeader(),
}) })

View file

@ -59,7 +59,7 @@ export const usePageFallback = (args: TArgs) => {
await updatePageDescription({ await updatePageDescription({
description_binary: encodedBinary, description_binary: encodedBinary,
description_html: html, description_html: html,
description: json, description_json: json,
}); });
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);

View file

@ -80,7 +80,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
id: string | undefined; id: string | undefined;
name: string | undefined; name: string | undefined;
logo_props: TLogoProps | undefined; logo_props: TLogoProps | undefined;
description: object | undefined; description_json: object | undefined;
description_html: string | undefined; description_html: string | undefined;
color: string | undefined; color: string | undefined;
label_ids: string[] | undefined; label_ids: string[] | undefined;
@ -117,7 +117,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
this.id = page?.id || undefined; this.id = page?.id || undefined;
this.name = page?.name; this.name = page?.name;
this.logo_props = page?.logo_props || undefined; this.logo_props = page?.logo_props || undefined;
this.description = page?.description || undefined; this.description_json = page?.description_json || undefined;
this.description_html = page?.description_html || undefined; this.description_html = page?.description_html || undefined;
this.color = page?.color || undefined; this.color = page?.color || undefined;
this.label_ids = page?.label_ids || undefined; this.label_ids = page?.label_ids || undefined;
@ -142,7 +142,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
id: observable.ref, id: observable.ref,
name: observable.ref, name: observable.ref,
logo_props: observable.ref, logo_props: observable.ref,
description: observable, description_json: observable.ref,
description_html: observable.ref, description_html: observable.ref,
color: observable.ref, color: observable.ref,
label_ids: observable, label_ids: observable,
@ -217,7 +217,7 @@ export class BasePage extends ExtendedBasePage implements TBasePage {
return { return {
id: this.id, id: this.id,
name: this.name, name: this.name,
description: this.description, description_json: this.description_json,
description_html: this.description_html, description_html: this.description_html,
color: this.color, color: this.color,
label_ids: this.label_ids, label_ids: this.label_ids,

View file

@ -215,7 +215,7 @@ export const convertHTMLDocumentToAllFormats = (args: TConvertHTMLDocumentToAllF
const { contentBinaryEncoded, contentHTML, contentJSON } = const { contentBinaryEncoded, contentHTML, contentJSON } =
getAllDocumentFormatsFromRichTextEditorBinaryData(contentBinary); getAllDocumentFormatsFromRichTextEditorBinaryData(contentBinary);
allFormats = { allFormats = {
description: contentJSON, description_json: contentJSON,
description_html: contentHTML, description_html: contentHTML,
description_binary: contentBinaryEncoded, description_binary: contentBinaryEncoded,
}; };
@ -228,7 +228,7 @@ export const convertHTMLDocumentToAllFormats = (args: TConvertHTMLDocumentToAllF
false false
); );
allFormats = { allFormats = {
description: contentJSON, description_json: contentJSON,
description_html: contentHTML, description_html: contentHTML,
description_binary: contentBinaryEncoded, description_binary: contentBinaryEncoded,
}; };

View file

@ -8,7 +8,7 @@ export type TPage = {
color: string | undefined; color: string | undefined;
created_at: Date | undefined; created_at: Date | undefined;
created_by: string | undefined; created_by: string | undefined;
description: object | undefined; description_json: object | undefined;
description_html: string | undefined; description_html: string | undefined;
id: string | undefined; id: string | undefined;
is_favorite: boolean; is_favorite: boolean;
@ -66,7 +66,7 @@ export type TPageVersion = {
export type TDocumentPayload = { export type TDocumentPayload = {
description_binary: string; description_binary: string;
description_html: string; description_html: string;
description: object; description_json: object;
}; };
export type TWebhookConnectionQueryParams = { export type TWebhookConnectionQueryParams = {