From 51a32f323dd79bf28c9926016310ab50cb624570 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:14:23 +0000 Subject: [PATCH 1/6] feat: track actual comment edits separately Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894 Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com> --- frontend/cypress/e2e/comment.cy.js | 2 ++ frontend/src/components/Comment.vue | 6 +++--- frontend/src/components/CommentsArea.vue | 11 +++++----- frontend/src/components/CommentsList.vue | 3 ++- frontend/src/types/doctypes.ts | 2 ++ .../doctype/gp_comment/gp_comment.json | 6 ++++++ .../gameplan/doctype/gp_comment/gp_comment.py | 8 ++++++++ gameplan/patches.txt | 1 + .../patches/backfill_gp_comment_edited_at.py | 20 +++++++++++++++++++ 9 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 gameplan/patches/backfill_gp_comment_edited_at.py diff --git a/frontend/cypress/e2e/comment.cy.js b/frontend/cypress/e2e/comment.cy.js index 64b945843..d1a35fce6 100644 --- a/frontend/cypress/e2e/comment.cy.js +++ b/frontend/cypress/e2e/comment.cy.js @@ -64,6 +64,7 @@ describe('Comment', () => { cy.get('button:contains("👍"):visible').click() cy.wait('@reactRequest') cy.get('button:contains("👍 1")').should('exist') + cy.get(`div[data-id=${comment.name}]`).contains('Edited').should('not.exist') // remove a reaction cy.get(`div[data-id=${comment.name}] button[aria-label="Add a reaction"]`).click() @@ -82,6 +83,7 @@ describe('Comment', () => { .type('{enter}@john{enter}', { delay: 100 }) // mention user cy.button('Submit').click() cy.get(`div[data-id=${comment.name}]`).contains('This is an edited comment').should('exist') + cy.get(`div[data-id=${comment.name}]`).contains('Edited').should('exist') cy.get( `div[data-id=${comment.name}] [data-type="mention"][data-id="john@example.com"]`, ).should('exist') diff --git a/frontend/src/components/Comment.vue b/frontend/src/components/Comment.vue index 0a58690e0..989296695 100644 --- a/frontend/src/components/Comment.vue +++ b/frontend/src/components/Comment.vue @@ -23,8 +23,8 @@  · Edited @@ -180,7 +180,7 @@ const dropdownOptions = computed(() => [ label: 'Revisions', icon: 'rotate-ccw', onClick: () => (showRevisionsDialog.value = true), - condition: () => props.comment.modified > props.comment.creation, + condition: () => Boolean(props.comment.edited_at), }, { label: 'Copy link', diff --git a/frontend/src/components/CommentsArea.vue b/frontend/src/components/CommentsArea.vue index bdb562b4c..576142536 100644 --- a/frontend/src/components/CommentsArea.vue +++ b/frontend/src/components/CommentsArea.vue @@ -222,11 +222,12 @@ const comments = useList({ 'name', 'content', 'owner', - 'creation', - 'modified', - 'deleted_at', - { reactions: ['name', 'user', 'emoji'] }, - ], + 'creation', + 'modified', + 'edited_at', + 'deleted_at', + { reactions: ['name', 'user', 'emoji'] }, + ], transform(data) { return data.map((d) => ({ ...d, doctype: 'GP Comment' })) }, diff --git a/frontend/src/components/CommentsList.vue b/frontend/src/components/CommentsList.vue index 9e1560e1b..97684f1be 100644 --- a/frontend/src/components/CommentsList.vue +++ b/frontend/src/components/CommentsList.vue @@ -130,7 +130,7 @@ const newCommentEditor = ref(null) const comments = useList< Pick< GPComment, - 'name' | 'content' | 'owner' | 'creation' | 'modified' | 'deleted_at' | 'reactions' + 'name' | 'content' | 'owner' | 'creation' | 'modified' | 'edited_at' | 'deleted_at' | 'reactions' > >({ doctype: 'GP Comment', @@ -141,6 +141,7 @@ const comments = useList< 'owner', 'creation', 'modified', + 'edited_at', 'deleted_at', { reactions: ['name', 'user', 'emoji'] }, ], diff --git a/frontend/src/types/doctypes.ts b/frontend/src/types/doctypes.ts index ad2065bbd..0d0b574a8 100644 --- a/frontend/src/types/doctypes.ts +++ b/frontend/src/types/doctypes.ts @@ -257,6 +257,8 @@ export interface GPComment extends DocType { reference_doctype?: string /** Reference Name: Dynamic Link (reference_doctype) */ reference_name?: string + /** Edited At: Datetime */ + edited_at?: string /** Reactions: Table (GP Reaction) */ reactions: GPReaction[] /** Deleted At: Datetime */ diff --git a/gameplan/gameplan/doctype/gp_comment/gp_comment.json b/gameplan/gameplan/doctype/gp_comment/gp_comment.json index b1b9e8112..1068a844f 100644 --- a/gameplan/gameplan/doctype/gp_comment/gp_comment.json +++ b/gameplan/gameplan/doctype/gp_comment/gp_comment.json @@ -9,6 +9,7 @@ "content", "reference_doctype", "reference_name", + "edited_at", "deleted_at", "reactions", "tags" @@ -34,6 +35,11 @@ "label": "Reference Name", "options": "reference_doctype" }, + { + "fieldname": "edited_at", + "fieldtype": "Datetime", + "label": "Edited At" + }, { "fieldname": "reactions", "fieldtype": "Table", diff --git a/gameplan/gameplan/doctype/gp_comment/gp_comment.py b/gameplan/gameplan/doctype/gp_comment/gp_comment.py index 206370efc..e8470f494 100644 --- a/gameplan/gameplan/doctype/gp_comment/gp_comment.py +++ b/gameplan/gameplan/doctype/gp_comment/gp_comment.py @@ -41,6 +41,7 @@ def after_delete(self): self.update_task_meta() def before_save(self): + self.set_edited_at() self.update_tags() def update_discussion_meta(self): @@ -68,6 +69,13 @@ def sanitize_content(self): self.content = remove_empty_trailing_paragraphs(self.content) self.content = sanitize_content(self.content) + def set_edited_at(self): + if self.is_new() or not self.get_doc_before_save(): + return + + if self.has_value_changed("content"): + self.edited_at = frappe.utils.now() + def on_update(self): self.notify_mentions() self.notify_reactions() diff --git a/gameplan/patches.txt b/gameplan/patches.txt index 0c2dec288..be920719c 100644 --- a/gameplan/patches.txt +++ b/gameplan/patches.txt @@ -29,3 +29,4 @@ gameplan.gameplan.doctype.gp_project_visit.patches.add_indexes gameplan.gameplan.doctype.gp_unread_record.patches.migrate_to_unread_records gameplan.gameplan.doctype.gp_pinned_project.patches.delete_pinned_projects_for_archived_spaces gameplan.gameplan.doctype.gp_discussion.patches.set_default_pin_scope +gameplan.patches.backfill_gp_comment_edited_at diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py new file mode 100644 index 000000000..59cc50071 --- /dev/null +++ b/gameplan/patches/backfill_gp_comment_edited_at.py @@ -0,0 +1,20 @@ +import frappe + + +def execute(): + versions = frappe.qb.get_query( + "Version", + fields=["docname", "creation"], + filters={ + "ref_doctype": "GP Comment", + "data": ["like", '%["content",%'], + }, + order_by="creation asc", + ).run(as_dict=True) + + latest_revisions = {} + for version in versions: + latest_revisions[version.docname] = version.creation + + for comment, edited_at in latest_revisions.items(): + frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False) From 5efb2b35fa06b9cd3071e0f12a9cb12501f30c96 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:15:02 +0000 Subject: [PATCH 2/6] style: format comment edit tracking changes Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894 Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com> --- frontend/src/components/CommentsArea.vue | 12 ++++++------ gameplan/patches/backfill_gp_comment_edited_at.py | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/CommentsArea.vue b/frontend/src/components/CommentsArea.vue index 576142536..413020f84 100644 --- a/frontend/src/components/CommentsArea.vue +++ b/frontend/src/components/CommentsArea.vue @@ -222,12 +222,12 @@ const comments = useList({ 'name', 'content', 'owner', - 'creation', - 'modified', - 'edited_at', - 'deleted_at', - { reactions: ['name', 'user', 'emoji'] }, - ], + 'creation', + 'modified', + 'edited_at', + 'deleted_at', + { reactions: ['name', 'user', 'emoji'] }, + ], transform(data) { return data.map((d) => ({ ...d, doctype: 'GP Comment' })) }, diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py index 59cc50071..21940654a 100644 --- a/gameplan/patches/backfill_gp_comment_edited_at.py +++ b/gameplan/patches/backfill_gp_comment_edited_at.py @@ -17,4 +17,5 @@ def execute(): latest_revisions[version.docname] = version.creation for comment, edited_at in latest_revisions.items(): - frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False) + if frappe.db.exists("GP Comment", comment): + frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False) From f903fc52ab6510bdb4c1076f18122c7f54252772 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:15:34 +0000 Subject: [PATCH 3/6] style: format comments list changes Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894 Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com> --- frontend/src/components/CommentsList.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/CommentsList.vue b/frontend/src/components/CommentsList.vue index 97684f1be..10aca6878 100644 --- a/frontend/src/components/CommentsList.vue +++ b/frontend/src/components/CommentsList.vue @@ -130,7 +130,14 @@ const newCommentEditor = ref(null) const comments = useList< Pick< GPComment, - 'name' | 'content' | 'owner' | 'creation' | 'modified' | 'edited_at' | 'deleted_at' | 'reactions' + | 'name' + | 'content' + | 'owner' + | 'creation' + | 'modified' + | 'edited_at' + | 'deleted_at' + | 'reactions' > >({ doctype: 'GP Comment', From db1adfdde2f4c86023885e69287d8113099bc3f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:17:59 +0000 Subject: [PATCH 4/6] perf: batch backfill comment edited timestamps Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894 Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com> --- .../patches/backfill_gp_comment_edited_at.py | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py index 21940654a..ae039e8e1 100644 --- a/gameplan/patches/backfill_gp_comment_edited_at.py +++ b/gameplan/patches/backfill_gp_comment_edited_at.py @@ -2,20 +2,18 @@ def execute(): - versions = frappe.qb.get_query( - "Version", - fields=["docname", "creation"], - filters={ - "ref_doctype": "GP Comment", - "data": ["like", '%["content",%'], - }, - order_by="creation asc", - ).run(as_dict=True) - - latest_revisions = {} - for version in versions: - latest_revisions[version.docname] = version.creation - - for comment, edited_at in latest_revisions.items(): - if frappe.db.exists("GP Comment", comment): - frappe.db.set_value("GP Comment", comment, "edited_at", edited_at, update_modified=False) + frappe.db.sql( + """ + update `tabGP Comment` comment + inner join ( + select docname, max(creation) as edited_at + from `tabVersion` + where ref_doctype = 'GP Comment' + and data like %s + group by docname + ) revisions on revisions.docname = comment.name + set comment.edited_at = revisions.edited_at + where comment.edited_at is null + """, + ('%["content",%',), + ) From 9c8b579d767c7de3e543f69c648cea0aa3797ef4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:19:09 +0000 Subject: [PATCH 5/6] refactor: simplify comment edited tracking Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/2c04736d-02c3-4486-be2e-8cbbe3547894 Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com> --- gameplan/gameplan/doctype/gp_comment/gp_comment.py | 2 +- gameplan/patches/backfill_gp_comment_edited_at.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gameplan/gameplan/doctype/gp_comment/gp_comment.py b/gameplan/gameplan/doctype/gp_comment/gp_comment.py index e8470f494..86480059e 100644 --- a/gameplan/gameplan/doctype/gp_comment/gp_comment.py +++ b/gameplan/gameplan/doctype/gp_comment/gp_comment.py @@ -70,7 +70,7 @@ def sanitize_content(self): self.content = sanitize_content(self.content) def set_edited_at(self): - if self.is_new() or not self.get_doc_before_save(): + if not self.get_doc_before_save(): return if self.has_value_changed("content"): diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/patches/backfill_gp_comment_edited_at.py index ae039e8e1..d27d38719 100644 --- a/gameplan/patches/backfill_gp_comment_edited_at.py +++ b/gameplan/patches/backfill_gp_comment_edited_at.py @@ -15,5 +15,6 @@ def execute(): set comment.edited_at = revisions.edited_at where comment.edited_at is null """, + # Version.data stores changed fields as JSON arrays like ["content", old, new]. ('%["content",%',), ) From 4a66c24f5d4ecd026affed0c1d154a97368e12c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 06:53:40 +0000 Subject: [PATCH 6/6] refactor: move gp comment patch under doctype Agent-Logs-Url: https://github.com/frappe/gameplan/sessions/cf2a5f48-b6e0-493b-b116-e29e7fd9152e Co-authored-by: netchampfaris <9355208+netchampfaris@users.noreply.github.com> --- .../gp_comment}/patches/backfill_gp_comment_edited_at.py | 0 gameplan/patches.txt | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename gameplan/{ => gameplan/doctype/gp_comment}/patches/backfill_gp_comment_edited_at.py (100%) diff --git a/gameplan/patches/backfill_gp_comment_edited_at.py b/gameplan/gameplan/doctype/gp_comment/patches/backfill_gp_comment_edited_at.py similarity index 100% rename from gameplan/patches/backfill_gp_comment_edited_at.py rename to gameplan/gameplan/doctype/gp_comment/patches/backfill_gp_comment_edited_at.py diff --git a/gameplan/patches.txt b/gameplan/patches.txt index be920719c..7f5e73994 100644 --- a/gameplan/patches.txt +++ b/gameplan/patches.txt @@ -29,4 +29,4 @@ gameplan.gameplan.doctype.gp_project_visit.patches.add_indexes gameplan.gameplan.doctype.gp_unread_record.patches.migrate_to_unread_records gameplan.gameplan.doctype.gp_pinned_project.patches.delete_pinned_projects_for_archived_spaces gameplan.gameplan.doctype.gp_discussion.patches.set_default_pin_scope -gameplan.patches.backfill_gp_comment_edited_at +gameplan.gameplan.doctype.gp_comment.patches.backfill_gp_comment_edited_at