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