diff --git a/frontend/cypress/e2e/comment.cy.js b/frontend/cypress/e2e/comment.cy.js index 64b94584..d1a35fce 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 0a58690e..98929669 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 bdb562b4..413020f8 100644 --- a/frontend/src/components/CommentsArea.vue +++ b/frontend/src/components/CommentsArea.vue @@ -224,6 +224,7 @@ const comments = useList({ 'owner', 'creation', 'modified', + 'edited_at', 'deleted_at', { reactions: ['name', 'user', 'emoji'] }, ], diff --git a/frontend/src/components/CommentsList.vue b/frontend/src/components/CommentsList.vue index 9e1560e1..10aca687 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' | 'deleted_at' | 'reactions' + | 'name' + | 'content' + | 'owner' + | 'creation' + | 'modified' + | 'edited_at' + | 'deleted_at' + | 'reactions' > >({ doctype: 'GP Comment', @@ -141,6 +148,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 ad2065bb..0d0b574a 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 b1b9e811..1068a844 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 206370ef..86480059 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 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/gameplan/doctype/gp_comment/patches/backfill_gp_comment_edited_at.py b/gameplan/gameplan/doctype/gp_comment/patches/backfill_gp_comment_edited_at.py new file mode 100644 index 00000000..d27d3871 --- /dev/null +++ b/gameplan/gameplan/doctype/gp_comment/patches/backfill_gp_comment_edited_at.py @@ -0,0 +1,20 @@ +import frappe + + +def execute(): + 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 + """, + # Version.data stores changed fields as JSON arrays like ["content", old, new]. + ('%["content",%',), + ) diff --git a/gameplan/patches.txt b/gameplan/patches.txt index 0c2dec28..7f5e7399 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.gameplan.doctype.gp_comment.patches.backfill_gp_comment_edited_at