[Web + Doc-Updater] Add track changes accepted notification (#32752)

* feat: update doc manager to return a list of contributors to the accepted change

* feat: add new notification type for accepting a tracked change

* update email with tracked changes accepted

* feat: update tests

* fix: feedback on consistent api and returns

* feat: adding new tests

* feat: self accepted changes shouldnt trigger notification, and using existing changesAccepted hook

* Add better subject and activity list for track change accepted (#33094)

* feat: add better activity list entry and subject header for accepted changes, to match other notifications

* feat: updating tests

* feat: updating accepting_user_id to just user_id

* fix: adding users in emailBuilder test to userCache

GitOrigin-RevId: 6114f77916b5f503b7bbbb5ca8fed99e58edc31b
This commit is contained in:
Jimmy Domagala-Tang
2026-04-29 11:21:18 -04:00
committed by Copybot
parent fe59b9cdb5
commit 994932b8e3
8 changed files with 138 additions and 24 deletions

View File

@@ -331,6 +331,7 @@ const DocumentManager = {
if (changeIds == null) {
changeIds = []
}
let changeContributors = []
const {
lines,
@@ -374,7 +375,7 @@ const DocumentManager = {
})
if (historyUpdates.length === 0) {
return
return changeContributors
}
await ProjectHistoryRedisManager.promises.queueOps(
@@ -382,6 +383,11 @@ const DocumentManager = {
...historyUpdates.map(op => JSON.stringify(op))
)
}
changeContributors = (ranges.changes || [])
.filter(change => changeIds.includes(change.id))
.map(change => change?.metadata?.user_id)
.filter(userId => userId)
return changeContributors
},
async rejectChanges(projectId, docId, changeIds, userId) {
@@ -708,12 +714,13 @@ const DocumentManager = {
async acceptChangesWithLock(projectId, docId, changeIds) {
const UpdateManager = require('./UpdateManager')
await UpdateManager.promises.lockUpdatesAndDo(
const changeContributors = await UpdateManager.promises.lockUpdatesAndDo(
DocumentManager.acceptChanges,
projectId,
docId,
changeIds
)
return changeContributors
},
async rejectChangesWithLock(projectId, docId, changeIds, userId) {

View File

@@ -355,17 +355,19 @@ async function acceptChanges(req, res) {
`accepting ${changeIds.length} changes via http`
)
const timer = new Metrics.Timer('http.acceptChanges')
await DocumentManager.promises.acceptChangesWithLock(
projectId,
docId,
changeIds
)
const changeContributors =
await DocumentManager.promises.acceptChangesWithLock(
projectId,
docId,
changeIds
)
timer.done()
logger.debug(
{ projectId, docId },
`accepted ${changeIds.length} changes via http`
)
res.sendStatus(204) // No Content
res.status(200).json({ changeContributors })
}
async function rejectChanges(req, res) {

View File

@@ -756,7 +756,18 @@ describe('DocumentManager', function () {
]
this.version = 34
this.lines = ['original', 'lines']
this.ranges = { entries: 'mock', comments: 'mock' }
this.ranges = {
entries: 'mock',
comments: 'mock',
changes: [
{ id: 'mock-change-id', metadata: { user_id: 'mock-user-id-0' } },
{ id: 'mock-change-id-1', metadata: { user_id: 'mock-user-id-1' } },
{ id: 'mock-change-id-2', metadata: { user_id: 'mock-user-id-2' } },
{ id: 'mock-change-id-3', metadata: { user_id: 'mock-user-id-3' } },
{ id: 'mock-change-id-4', metadata: { user_id: 'mock-user-id-4' } },
{ id: 'other-change-id', metadata: { user_id: 'other-user-id' } },
],
}
this.updated_ranges = { entries: 'updated', comments: 'updated' }
this.DocumentManager.promises.getDoc = sinon.stub().resolves({
lines: this.lines,
@@ -768,7 +779,7 @@ describe('DocumentManager', function () {
describe('successfully with a single change', function () {
beforeEach(async function () {
await this.DocumentManager.promises.acceptChanges(
this.result = await this.DocumentManager.promises.acceptChanges(
this.project_id,
this.doc_id,
[this.change_id]
@@ -803,11 +814,15 @@ describe('DocumentManager', function () {
)
.should.equal(true)
})
it('should return the change contributors', function () {
expect(this.result).to.deep.equal(['mock-user-id-0'])
})
})
describe('successfully with multiple changes', function () {
beforeEach(async function () {
await this.DocumentManager.promises.acceptChanges(
this.result = await this.DocumentManager.promises.acceptChanges(
this.project_id,
this.doc_id,
this.change_ids
@@ -824,6 +839,15 @@ describe('DocumentManager', function () {
)
.should.equal(true)
})
it('should return the change contributors', function () {
expect(this.result).to.deep.equal([
'mock-user-id-1',
'mock-user-id-2',
'mock-user-id-3',
'mock-user-id-4',
])
})
})
describe('when the doc is not found', function () {

View File

@@ -14,6 +14,7 @@ describe('HttpController', function () {
send: sinon.stub(),
sendStatus: sinon.stub(),
json: sinon.stub(),
status: sinon.stub().returnsThis(),
}
this.DocumentManager = {
@@ -753,6 +754,10 @@ describe('HttpController', function () {
describe('successfully with a single change', function () {
beforeEach(async function () {
this.changeContributors = ['user-id-1', 'user-id-2']
this.DocumentManager.promises.acceptChangesWithLock.resolves(
this.changeContributors
)
await this.HttpController.acceptChanges(this.req, this.res, this.next)
})
@@ -764,8 +769,11 @@ describe('HttpController', function () {
)
})
it('should return a successful No Content response', function () {
this.res.sendStatus.calledWith(204).should.equal(true)
it('should return a successful 200 with a list of the change contributors', function () {
this.res.status.should.have.been.calledWith(200)
this.res.json.should.have.been.calledWith({
changeContributors: this.changeContributors,
})
})
it('should log the request', function () {