mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #214 from sharelatex/hof-version-entity-deletion
version entity deletion
This commit is contained in:
commit
e18dc1e41d
15 changed files with 285 additions and 83 deletions
|
@ -207,8 +207,8 @@ module.exports = DocumentUpdaterHandler =
|
||||||
updateProjectStructure : (project_id, userId, changes, callback = (error) ->)->
|
updateProjectStructure : (project_id, userId, changes, callback = (error) ->)->
|
||||||
return callback() if !settings.apis.project_history?.enabled
|
return callback() if !settings.apis.project_history?.enabled
|
||||||
|
|
||||||
docUpdates = DocumentUpdaterHandler._getRenameUpdates('doc', changes.oldDocs, changes.newDocs)
|
docUpdates = DocumentUpdaterHandler._getUpdates('doc', changes.oldDocs, changes.newDocs)
|
||||||
fileUpdates = DocumentUpdaterHandler._getRenameUpdates('file', changes.oldFiles, changes.newFiles)
|
fileUpdates = DocumentUpdaterHandler._getUpdates('file', changes.oldFiles, changes.newFiles)
|
||||||
|
|
||||||
timer = new metrics.Timer("set-document")
|
timer = new metrics.Timer("set-document")
|
||||||
url = "#{settings.apis.documentupdater.url}/project/#{project_id}"
|
url = "#{settings.apis.documentupdater.url}/project/#{project_id}"
|
||||||
|
@ -230,7 +230,7 @@ module.exports = DocumentUpdaterHandler =
|
||||||
logger.error {project_id, url}, "doc updater returned a non-success status code: #{res.statusCode}"
|
logger.error {project_id, url}, "doc updater returned a non-success status code: #{res.statusCode}"
|
||||||
callback new Error("doc updater returned a non-success status code: #{res.statusCode}")
|
callback new Error("doc updater returned a non-success status code: #{res.statusCode}")
|
||||||
|
|
||||||
_getRenameUpdates: (entityType, oldEntities, newEntities) ->
|
_getUpdates: (entityType, oldEntities, newEntities) ->
|
||||||
oldEntities ||= []
|
oldEntities ||= []
|
||||||
newEntities ||= []
|
newEntities ||= []
|
||||||
updates = []
|
updates = []
|
||||||
|
@ -255,6 +255,16 @@ module.exports = DocumentUpdaterHandler =
|
||||||
pathname: oldEntity.path
|
pathname: oldEntity.path
|
||||||
newPathname: newEntity.path
|
newPathname: newEntity.path
|
||||||
|
|
||||||
|
for id, oldEntity of oldEntitiesHash
|
||||||
|
newEntity = newEntitiesHash[id]
|
||||||
|
|
||||||
|
if !newEntity?
|
||||||
|
# entity deleted
|
||||||
|
updates.push
|
||||||
|
id: id
|
||||||
|
pathname: oldEntity.path
|
||||||
|
newPathname: ''
|
||||||
|
|
||||||
updates
|
updates
|
||||||
|
|
||||||
PENDINGUPDATESKEY = "PendingUpdates"
|
PENDINGUPDATESKEY = "PendingUpdates"
|
||||||
|
|
|
@ -105,19 +105,19 @@ module.exports = EditorController =
|
||||||
async.series jobs, (err)->
|
async.series jobs, (err)->
|
||||||
callback err, newFolders, lastFolder
|
callback err, newFolders, lastFolder
|
||||||
|
|
||||||
deleteEntity : (project_id, entity_id, entityType, source, callback)->
|
deleteEntity : (project_id, entity_id, entityType, source, userId, callback)->
|
||||||
LockManager.getLock project_id, (err)->
|
LockManager.getLock project_id, (err)->
|
||||||
if err?
|
if err?
|
||||||
logger.err err:err, project_id:project_id, "could not get lock to deleteEntity"
|
logger.err err:err, project_id:project_id, "could not get lock to deleteEntity"
|
||||||
return callback(err)
|
return callback(err)
|
||||||
EditorController.deleteEntityWithoutLock project_id, entity_id, entityType, source, (err)->
|
EditorController.deleteEntityWithoutLock project_id, entity_id, entityType, source, userId, (err)->
|
||||||
LockManager.releaseLock project_id, ()->
|
LockManager.releaseLock project_id, ()->
|
||||||
callback(err)
|
callback(err)
|
||||||
|
|
||||||
deleteEntityWithoutLock: (project_id, entity_id, entityType, source, callback)->
|
deleteEntityWithoutLock: (project_id, entity_id, entityType, source, userId, callback)->
|
||||||
logger.log {project_id, entity_id, entityType, source}, "start delete process of entity"
|
logger.log {project_id, entity_id, entityType, source}, "start delete process of entity"
|
||||||
Metrics.inc "editor.delete-entity"
|
Metrics.inc "editor.delete-entity"
|
||||||
ProjectEntityHandler.deleteEntity project_id, entity_id, entityType, (err)->
|
ProjectEntityHandler.deleteEntity project_id, entity_id, entityType, userId, (err)->
|
||||||
if err?
|
if err?
|
||||||
logger.err err:err, project_id:project_id, entity_id:entity_id, entityType:entityType, "error deleting entity"
|
logger.err err:err, project_id:project_id, entity_id:entity_id, entityType:entityType, "error deleting entity"
|
||||||
return callback(err)
|
return callback(err)
|
||||||
|
|
|
@ -147,6 +147,7 @@ module.exports = EditorHttpController =
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
entity_id = req.params.entity_id
|
entity_id = req.params.entity_id
|
||||||
entity_type = req.params.entity_type
|
entity_type = req.params.entity_type
|
||||||
EditorController.deleteEntity project_id, entity_id, entity_type, "editor", (error) ->
|
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||||
|
EditorController.deleteEntity project_id, entity_id, entity_type, "editor", user_id, (error) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
res.sendStatus 204
|
res.sendStatus 204
|
||||||
|
|
|
@ -412,7 +412,7 @@ module.exports = ProjectEntityHandler =
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
|
|
||||||
deleteEntity: (project_id, entity_id, entityType, callback = (error) ->)->
|
deleteEntity: (project_id, entity_id, entityType, userId, callback = (error) ->)->
|
||||||
self = @
|
self = @
|
||||||
logger.log entity_id:entity_id, entityType:entityType, project_id:project_id, "deleting project entity"
|
logger.log entity_id:entity_id, entityType:entityType, project_id:project_id, "deleting project entity"
|
||||||
if !entityType?
|
if !entityType?
|
||||||
|
@ -423,7 +423,7 @@ module.exports = ProjectEntityHandler =
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
projectLocator.findElement {project: project, element_id: entity_id, type: entityType}, (error, entity, path)=>
|
projectLocator.findElement {project: project, element_id: entity_id, type: entityType}, (error, entity, path)=>
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
ProjectEntityHandler._cleanUpEntity project, entity, entityType, (error) ->
|
ProjectEntityHandler._cleanUpEntity project, entity, entityType, path.fileSystem, userId, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
tpdsUpdateSender.deleteEntity project_id:project_id, path:path.fileSystem, project_name:project.name, (error) ->
|
tpdsUpdateSender.deleteEntity project_id:project_id, path:path.fileSystem, project_name:project.name, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
@ -456,17 +456,17 @@ module.exports = ProjectEntityHandler =
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback
|
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback
|
||||||
|
|
||||||
_cleanUpEntity: (project, entity, entityType, callback = (error) ->) ->
|
_cleanUpEntity: (project, entity, entityType, path, userId, callback = (error) ->) ->
|
||||||
if(entityType.indexOf("file") != -1)
|
if(entityType.indexOf("file") != -1)
|
||||||
ProjectEntityHandler._cleanUpFile project, entity, callback
|
ProjectEntityHandler._cleanUpFile project, entity, path, userId, callback
|
||||||
else if (entityType.indexOf("doc") != -1)
|
else if (entityType.indexOf("doc") != -1)
|
||||||
ProjectEntityHandler._cleanUpDoc project, entity, callback
|
ProjectEntityHandler._cleanUpDoc project, entity, path, userId, callback
|
||||||
else if (entityType.indexOf("folder") != -1)
|
else if (entityType.indexOf("folder") != -1)
|
||||||
ProjectEntityHandler._cleanUpFolder project, entity, callback
|
ProjectEntityHandler._cleanUpFolder project, entity, path, userId, callback
|
||||||
else
|
else
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
_cleanUpDoc: (project, doc, callback = (error) ->) ->
|
_cleanUpDoc: (project, doc, path, userId, callback = (error) ->) ->
|
||||||
project_id = project._id.toString()
|
project_id = project._id.toString()
|
||||||
doc_id = doc._id.toString()
|
doc_id = doc._id.toString()
|
||||||
unsetRootDocIfRequired = (callback) =>
|
unsetRootDocIfRequired = (callback) =>
|
||||||
|
@ -483,26 +483,33 @@ module.exports = ProjectEntityHandler =
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
DocstoreManager.deleteDoc project_id, doc_id, (error) ->
|
DocstoreManager.deleteDoc project_id, doc_id, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback()
|
changes = oldDocs: [ {doc, path} ]
|
||||||
|
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||||
|
|
||||||
_cleanUpFile: (project, file, callback = (error) ->) ->
|
_cleanUpFile: (project, file, path, userId, callback = (error) ->) ->
|
||||||
project_id = project._id.toString()
|
project_id = project._id.toString()
|
||||||
file_id = file._id.toString()
|
file_id = file._id.toString()
|
||||||
FileStoreHandler.deleteFile project_id, file_id, callback
|
FileStoreHandler.deleteFile project_id, file_id, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
changes = oldFiles: [ {file, path} ]
|
||||||
|
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||||
|
|
||||||
_cleanUpFolder: (project, folder, callback = (error) ->) ->
|
_cleanUpFolder: (project, folder, folderPath, userId, callback = (error) ->) ->
|
||||||
jobs = []
|
jobs = []
|
||||||
for doc in folder.docs
|
for doc in folder.docs
|
||||||
do (doc) ->
|
do (doc) ->
|
||||||
jobs.push (callback) -> ProjectEntityHandler._cleanUpDoc project, doc, callback
|
docPath = path.join(folderPath, doc.name)
|
||||||
|
jobs.push (callback) -> ProjectEntityHandler._cleanUpDoc project, doc, docPath, userId, callback
|
||||||
|
|
||||||
for file in folder.fileRefs
|
for file in folder.fileRefs
|
||||||
do (file) ->
|
do (file) ->
|
||||||
jobs.push (callback) -> ProjectEntityHandler._cleanUpFile project, file, callback
|
filePath = path.join(folderPath, file.name)
|
||||||
|
jobs.push (callback) -> ProjectEntityHandler._cleanUpFile project, file, filePath, userId, callback
|
||||||
|
|
||||||
for childFolder in folder.folders
|
for childFolder in folder.folders
|
||||||
do (childFolder) ->
|
do (childFolder) ->
|
||||||
jobs.push (callback) -> ProjectEntityHandler._cleanUpFolder project, childFolder, callback
|
folderPath = path.join(folderPath, childFolder.name)
|
||||||
|
jobs.push (callback) -> ProjectEntityHandler._cleanUpFolder project, childFolder, folderPath, userId, callback
|
||||||
|
|
||||||
async.series jobs, callback
|
async.series jobs, callback
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ module.exports =
|
||||||
logger.log user_id:user_id, filePath:path, projectName:projectName, project_id:project._id, "project found for delete update, path is root so marking project as deleted"
|
logger.log user_id:user_id, filePath:path, projectName:projectName, project_id:project._id, "project found for delete update, path is root so marking project as deleted"
|
||||||
return projectDeleter.markAsDeletedByExternalSource project._id, callback
|
return projectDeleter.markAsDeletedByExternalSource project._id, callback
|
||||||
else
|
else
|
||||||
updateMerger.deleteUpdate project._id, path, source, (err)->
|
updateMerger.deleteUpdate user_id, project._id, path, source, (err)->
|
||||||
callback(err)
|
callback(err)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,13 +32,13 @@ module.exports =
|
||||||
else
|
else
|
||||||
self.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback
|
self.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback
|
||||||
|
|
||||||
deleteUpdate: (project_id, path, source, callback)->
|
deleteUpdate: (user_id, project_id, path, source, callback)->
|
||||||
projectLocator.findElementByPath project_id, path, (err, element, type)->
|
projectLocator.findElementByPath project_id, path, (err, element, type)->
|
||||||
if err? || !element?
|
if err? || !element?
|
||||||
logger.log element:element, project_id:project_id, path:path, "could not find entity for deleting, assuming it was already deleted"
|
logger.log element:element, project_id:project_id, path:path, "could not find entity for deleting, assuming it was already deleted"
|
||||||
return callback()
|
return callback()
|
||||||
logger.log project_id:project_id, path:path, type:type, element:element, "processing update to delete entity from tpds"
|
logger.log project_id:project_id, path:path, type:type, element:element, "processing update to delete entity from tpds"
|
||||||
editorController.deleteEntity project_id, element._id, type, source, (err)->
|
editorController.deleteEntity project_id, element._id, type, source, user_id, (err)->
|
||||||
logger.log project_id:project_id, path:path, "finished processing update to delete entity from tpds"
|
logger.log project_id:project_id, path:path, "finished processing update to delete entity from tpds"
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,7 @@ describe "ProjectStructureChanges", ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
if res.statusCode < 200 || res.statusCode >= 300
|
if res.statusCode < 200 || res.statusCode >= 300
|
||||||
throw new Error("failed to add doc #{res.statusCode}")
|
throw new Error("failed to add doc #{res.statusCode}")
|
||||||
|
@example_doc_id = body._id
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "should version the doc added", ->
|
it "should version the doc added", ->
|
||||||
|
@ -162,6 +163,8 @@ describe "ProjectStructureChanges", ->
|
||||||
if res.statusCode < 200 || res.statusCode >= 300
|
if res.statusCode < 200 || res.statusCode >= 300
|
||||||
throw new Error("failed to upload file #{res.statusCode}")
|
throw new Error("failed to upload file #{res.statusCode}")
|
||||||
|
|
||||||
|
@example_file_id = JSON.parse(body).entity_id
|
||||||
|
|
||||||
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
|
||||||
expect(updates.length).to.equal(1)
|
expect(updates.length).to.equal(1)
|
||||||
update = updates[0]
|
update = updates[0]
|
||||||
|
@ -199,6 +202,120 @@ describe "ProjectStructureChanges", ->
|
||||||
|
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
describe "moving entities", ->
|
||||||
|
before (done) ->
|
||||||
|
@owner.request.post {
|
||||||
|
uri: "project/#{@example_project_id}/folder",
|
||||||
|
formData:
|
||||||
|
name: 'foo'
|
||||||
|
}, (error, res, body) =>
|
||||||
|
throw error if error?
|
||||||
|
@example_folder_id_1 = JSON.parse(body)._id
|
||||||
|
done()
|
||||||
|
|
||||||
|
beforeEach () ->
|
||||||
|
MockDocUpdaterApi.clearProjectStructureUpdates()
|
||||||
|
|
||||||
|
it "should version moving a doc", (done) ->
|
||||||
|
@owner.request.post {
|
||||||
|
uri: "project/#{@example_project_id}/Doc/#{@example_doc_id}/move",
|
||||||
|
json:
|
||||||
|
folder_id: @example_folder_id_1
|
||||||
|
}, (error, res, body) =>
|
||||||
|
throw error if error?
|
||||||
|
if res.statusCode < 200 || res.statusCode >= 300
|
||||||
|
throw new Error("failed to move doc #{res.statusCode}")
|
||||||
|
|
||||||
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
|
||||||
|
expect(updates.length).to.equal(1)
|
||||||
|
update = updates[0]
|
||||||
|
expect(update.userId).to.equal(@owner._id)
|
||||||
|
expect(update.pathname).to.equal("/new.tex")
|
||||||
|
expect(update.newPathname).to.equal("/foo/new.tex")
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "should version moving a file", (done) ->
|
||||||
|
@owner.request.post {
|
||||||
|
uri: "project/#{@example_project_id}/File/#{@example_file_id}/move",
|
||||||
|
json:
|
||||||
|
folder_id: @example_folder_id_1
|
||||||
|
}, (error, res, body) =>
|
||||||
|
throw error if error?
|
||||||
|
if res.statusCode < 200 || res.statusCode >= 300
|
||||||
|
throw new Error("failed to move file #{res.statusCode}")
|
||||||
|
|
||||||
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
|
||||||
|
expect(updates.length).to.equal(1)
|
||||||
|
update = updates[0]
|
||||||
|
expect(update.userId).to.equal(@owner._id)
|
||||||
|
expect(update.pathname).to.equal("/1pixel.png")
|
||||||
|
expect(update.newPathname).to.equal("/foo/1pixel.png")
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
|
it "should version moving a folder", (done) ->
|
||||||
|
@owner.request.post {
|
||||||
|
uri: "project/#{@example_project_id}/folder",
|
||||||
|
formData:
|
||||||
|
name: 'bar'
|
||||||
|
}, (error, res, body) =>
|
||||||
|
throw error if error?
|
||||||
|
@example_folder_id_2 = JSON.parse(body)._id
|
||||||
|
|
||||||
|
@owner.request.post {
|
||||||
|
uri: "project/#{@example_project_id}/Folder/#{@example_folder_id_1}/move",
|
||||||
|
json:
|
||||||
|
folder_id: @example_folder_id_2
|
||||||
|
}, (error, res, body) =>
|
||||||
|
throw error if error?
|
||||||
|
if res.statusCode < 200 || res.statusCode >= 300
|
||||||
|
throw new Error("failed to move folder #{res.statusCode}")
|
||||||
|
|
||||||
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
|
||||||
|
expect(updates.length).to.equal(1)
|
||||||
|
update = updates[0]
|
||||||
|
expect(update.userId).to.equal(@owner._id)
|
||||||
|
expect(update.pathname).to.equal("/foo/new.tex")
|
||||||
|
expect(update.newPathname).to.equal("/bar/foo/new.tex")
|
||||||
|
|
||||||
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
|
||||||
|
expect(updates.length).to.equal(1)
|
||||||
|
update = updates[0]
|
||||||
|
expect(update.userId).to.equal(@owner._id)
|
||||||
|
expect(update.pathname).to.equal("/foo/1pixel.png")
|
||||||
|
expect(update.newPathname).to.equal("/bar/foo/1pixel.png")
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
|
describe "deleting entities", ->
|
||||||
|
beforeEach () ->
|
||||||
|
MockDocUpdaterApi.clearProjectStructureUpdates()
|
||||||
|
|
||||||
|
it "should version deleting a folder", (done) ->
|
||||||
|
@owner.request.delete {
|
||||||
|
uri: "project/#{@example_project_id}/Folder/#{@example_folder_id_2}",
|
||||||
|
}, (error, res, body) =>
|
||||||
|
throw error if error?
|
||||||
|
if res.statusCode < 200 || res.statusCode >= 300
|
||||||
|
throw new Error("failed to delete folder #{res.statusCode}")
|
||||||
|
|
||||||
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
|
||||||
|
expect(updates.length).to.equal(1)
|
||||||
|
update = updates[0]
|
||||||
|
expect(update.userId).to.equal(@owner._id)
|
||||||
|
expect(update.pathname).to.equal("/bar/foo/new.tex")
|
||||||
|
expect(update.newPathname).to.equal("")
|
||||||
|
|
||||||
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
|
||||||
|
expect(updates.length).to.equal(1)
|
||||||
|
update = updates[0]
|
||||||
|
expect(update.userId).to.equal(@owner._id)
|
||||||
|
expect(update.pathname).to.equal("/bar/foo/1pixel.png")
|
||||||
|
expect(update.newPathname).to.equal("")
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
describe "tpds", ->
|
describe "tpds", ->
|
||||||
before (done) ->
|
before (done) ->
|
||||||
@tpds_project_name = "tpds-project-#{new ObjectId().toString()}"
|
@tpds_project_name = "tpds-project-#{new ObjectId().toString()}"
|
||||||
|
@ -305,3 +422,25 @@ describe "ProjectStructureChanges", ->
|
||||||
done()
|
done()
|
||||||
|
|
||||||
image_file.pipe(req)
|
image_file.pipe(req)
|
||||||
|
|
||||||
|
it "should version deleting a doc", (done) ->
|
||||||
|
req = @owner.request.delete {
|
||||||
|
uri: "/user/#{@owner._id}/update/#{@tpds_project_name}/test.tex",
|
||||||
|
auth:
|
||||||
|
user: _.keys(Settings.httpAuthUsers)[0]
|
||||||
|
pass: _.values(Settings.httpAuthUsers)[0]
|
||||||
|
sendImmediately: true
|
||||||
|
}, (error, res, body) =>
|
||||||
|
throw error if error?
|
||||||
|
if res.statusCode < 200 || res.statusCode >= 300
|
||||||
|
throw new Error("failed to delete doc #{res.statusCode}")
|
||||||
|
|
||||||
|
updates = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id).docUpdates
|
||||||
|
expect(updates.length).to.equal(1)
|
||||||
|
update = updates[0]
|
||||||
|
expect(update.userId).to.equal(@owner._id)
|
||||||
|
expect(update.pathname).to.equal("/test.tex")
|
||||||
|
expect(update.newPathname).to.equal("")
|
||||||
|
|
||||||
|
done()
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ module.exports = MockDocUpdaterApi =
|
||||||
@addProjectStructureUpdates(project_id, userId, docUpdates, fileUpdates)
|
@addProjectStructureUpdates(project_id, userId, docUpdates, fileUpdates)
|
||||||
res.sendStatus 200
|
res.sendStatus 200
|
||||||
|
|
||||||
|
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
|
||||||
|
res.send 204
|
||||||
|
|
||||||
app.listen 3003, (error) ->
|
app.listen 3003, (error) ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
.on "error", (error) ->
|
.on "error", (error) ->
|
||||||
|
|
|
@ -23,6 +23,16 @@ module.exports = MockDocStoreApi =
|
||||||
docs = (doc for doc_id, doc of @docs[req.params.project_id])
|
docs = (doc for doc_id, doc of @docs[req.params.project_id])
|
||||||
res.send JSON.stringify docs
|
res.send JSON.stringify docs
|
||||||
|
|
||||||
|
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
|
||||||
|
{project_id, doc_id} = req.params
|
||||||
|
if !@docs[project_id]?
|
||||||
|
res.send 404
|
||||||
|
else if !@docs[project_id][doc_id]?
|
||||||
|
res.send 404
|
||||||
|
else
|
||||||
|
@docs[project_id][doc_id] = undefined
|
||||||
|
res.send 204
|
||||||
|
|
||||||
app.listen 3016, (error) ->
|
app.listen 3016, (error) ->
|
||||||
throw error if error?
|
throw error if error?
|
||||||
.on "error", (error) ->
|
.on "error", (error) ->
|
||||||
|
|
|
@ -478,14 +478,22 @@ describe 'DocumentUpdaterHandler', ->
|
||||||
.should.equal true
|
.should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
describe "when a doc has been deleted", ->
|
describe "when an entity has been deleted", ->
|
||||||
it 'should do nothing', (done) ->
|
it 'should end the structure update to the document updater', (done) ->
|
||||||
@docId = new ObjectId()
|
@docId = new ObjectId()
|
||||||
@changes = oldDocs: [
|
@changes = oldDocs: [
|
||||||
{ path: '/foo', docLines: 'a\nb', doc: _id: @docId }
|
{ path: '/foo', docLines: 'a\nb', doc: _id: @docId }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
docUpdates = [
|
||||||
|
id: @docId.toString(),
|
||||||
|
pathname: '/foo',
|
||||||
|
newPathname: ''
|
||||||
|
]
|
||||||
|
|
||||||
@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
|
@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
|
||||||
@request.post.called.should.equal false
|
@request.post
|
||||||
|
.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id})
|
||||||
|
.should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
|
|
@ -376,58 +376,53 @@ describe "EditorController", ->
|
||||||
err.should.equal "timed out"
|
err.should.equal "timed out"
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
|
||||||
describe "deleteEntity", ->
|
describe "deleteEntity", ->
|
||||||
|
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@LockManager.getLock.callsArgWith(1)
|
@LockManager.getLock.callsArgWith(1)
|
||||||
@LockManager.releaseLock.callsArgWith(1)
|
@LockManager.releaseLock.callsArgWith(1)
|
||||||
@EditorController.deleteEntityWithoutLock = sinon.stub().callsArgWith(4)
|
@EditorController.deleteEntityWithoutLock = sinon.stub().callsArgWith(5)
|
||||||
|
|
||||||
it "should call deleteEntityWithoutLock", (done)->
|
it "should call deleteEntityWithoutLock", (done)->
|
||||||
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, =>
|
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, @user_id, =>
|
||||||
@EditorController.deleteEntityWithoutLock.calledWith(@project_id, @entity_id, @type, @source).should.equal true
|
@EditorController.deleteEntityWithoutLock
|
||||||
|
.calledWith(@project_id, @entity_id, @type, @source, @user_id)
|
||||||
|
.should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "should take the lock", (done)->
|
it "should take the lock", (done)->
|
||||||
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, =>
|
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, @user_id, =>
|
||||||
@LockManager.getLock.calledWith(@project_id).should.equal true
|
@LockManager.getLock.calledWith(@project_id).should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "should release the lock", (done)->
|
it "should release the lock", (done)->
|
||||||
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, (error)=>
|
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, @user_id, (error) =>
|
||||||
@LockManager.releaseLock.calledWith(@project_id).should.equal true
|
@LockManager.releaseLock.calledWith(@project_id).should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it "should error if it can't cat the lock", (done)->
|
it "should error if it can't cat the lock", (done)->
|
||||||
@LockManager.getLock = sinon.stub().callsArgWith(1, "timed out")
|
@LockManager.getLock = sinon.stub().callsArgWith(1, "timed out")
|
||||||
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, (err)=>
|
@EditorController.deleteEntity @project_id, @entity_id, @type, @source, @user_id, (error) =>
|
||||||
expect(err).to.exist
|
expect(error).to.exist
|
||||||
err.should.equal "timed out"
|
error.should.equal "timed out"
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe 'deleteEntityWithoutLock', ->
|
describe 'deleteEntityWithoutLock', ->
|
||||||
beforeEach ->
|
beforeEach (done) ->
|
||||||
@ProjectEntityHandler.deleteEntity = (project_id, entity_id, type, callback)-> callback()
|
|
||||||
@entity_id = "entity_id_here"
|
@entity_id = "entity_id_here"
|
||||||
@type = "doc"
|
@type = "doc"
|
||||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||||
|
@ProjectEntityHandler.deleteEntity = sinon.stub().callsArg(4)
|
||||||
|
@EditorController.deleteEntityWithoutLock @project_id, @entity_id, @type, @source, @user_id, done
|
||||||
|
|
||||||
it 'should delete the folder using the project entity handler', (done)->
|
it 'should delete the folder using the project entity handler', ->
|
||||||
mock = sinon.mock(@ProjectEntityHandler).expects("deleteEntity").withArgs(@project_id, @entity_id, @type).callsArg(3)
|
@ProjectEntityHandler.deleteEntity
|
||||||
|
.calledWith(@project_id, @entity_id, @type, @user_id)
|
||||||
|
.should.equal.true
|
||||||
|
|
||||||
@EditorController.deleteEntityWithoutLock @project_id, @entity_id, @type, @source, ->
|
it 'notify users an entity has been deleted', ->
|
||||||
mock.verify()
|
|
||||||
done()
|
|
||||||
|
|
||||||
it 'notify users an entity has been deleted', (done)->
|
|
||||||
@EditorController.deleteEntityWithoutLock @project_id, @entity_id, @type, @source, =>
|
|
||||||
@EditorRealTimeController.emitToRoom
|
@EditorRealTimeController.emitToRoom
|
||||||
.calledWith(@project_id, "removeEntity", @entity_id, @source)
|
.calledWith(@project_id, "removeEntity", @entity_id, @source)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
done()
|
|
||||||
|
|
||||||
describe "getting a list of project paths", ->
|
describe "getting a list of project paths", ->
|
||||||
|
|
||||||
|
|
|
@ -331,12 +331,12 @@ describe "EditorHttpController", ->
|
||||||
Project_id: @project_id
|
Project_id: @project_id
|
||||||
entity_id: @entity_id = "entity-id-123"
|
entity_id: @entity_id = "entity-id-123"
|
||||||
entity_type: @entity_type = "entity-type"
|
entity_type: @entity_type = "entity-type"
|
||||||
@EditorController.deleteEntity = sinon.stub().callsArg(4)
|
@EditorController.deleteEntity = sinon.stub().callsArg(5)
|
||||||
@EditorHttpController.deleteEntity @req, @res
|
@EditorHttpController.deleteEntity @req, @res
|
||||||
|
|
||||||
it "should call EditorController.deleteEntity", ->
|
it "should call EditorController.deleteEntity", ->
|
||||||
@EditorController.deleteEntity
|
@EditorController.deleteEntity
|
||||||
.calledWith(@project_id, @entity_id, @entity_type, "editor")
|
.calledWith(@project_id, @entity_id, @entity_type, "editor", @userId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
it "should send back a success response", ->
|
it "should send back a success response", ->
|
||||||
|
|
|
@ -157,13 +157,13 @@ describe 'ProjectEntityHandler', ->
|
||||||
@ProjectGetter.getProject.callsArgWith(2, null, @project)
|
@ProjectGetter.getProject.callsArgWith(2, null, @project)
|
||||||
@tpdsUpdateSender.deleteEntity = sinon.stub().callsArg(1)
|
@tpdsUpdateSender.deleteEntity = sinon.stub().callsArg(1)
|
||||||
@ProjectEntityHandler._removeElementFromMongoArray = sinon.stub().callsArg(3)
|
@ProjectEntityHandler._removeElementFromMongoArray = sinon.stub().callsArg(3)
|
||||||
@ProjectEntityHandler._cleanUpEntity = sinon.stub().callsArg(3)
|
@ProjectEntityHandler._cleanUpEntity = sinon.stub().callsArg(5)
|
||||||
@path = mongo: "mongo.path", fileSystem: "/file/system/path"
|
@path = mongo: "mongo.path", fileSystem: "/file/system/path"
|
||||||
@projectLocator.findElement = sinon.stub().callsArgWith(1, null, @entity = { _id: entity_id }, @path)
|
@projectLocator.findElement = sinon.stub().callsArgWith(1, null, @entity = { _id: entity_id }, @path)
|
||||||
|
|
||||||
describe "deleting from Mongo", ->
|
describe "deleting from Mongo", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@ProjectEntityHandler.deleteEntity project_id, entity_id, @type = 'file', done
|
@ProjectEntityHandler.deleteEntity project_id, entity_id, @type = 'file', userId, done
|
||||||
|
|
||||||
it "should retreive the path", ->
|
it "should retreive the path", ->
|
||||||
@projectLocator.findElement.called.should.equal true
|
@projectLocator.findElement.called.should.equal true
|
||||||
|
@ -182,7 +182,7 @@ describe 'ProjectEntityHandler', ->
|
||||||
|
|
||||||
it "should clean up the entity from the rest of the system", ->
|
it "should clean up the entity from the rest of the system", ->
|
||||||
@ProjectEntityHandler._cleanUpEntity
|
@ProjectEntityHandler._cleanUpEntity
|
||||||
.calledWith(@project, @entity, @type)
|
.calledWith(@project, @entity, @type, @path.fileSystem, userId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe "_cleanUpEntity", ->
|
describe "_cleanUpEntity", ->
|
||||||
|
@ -193,7 +193,9 @@ describe 'ProjectEntityHandler', ->
|
||||||
|
|
||||||
describe "a file", ->
|
describe "a file", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@ProjectEntityHandler._cleanUpEntity @project, _id: @entity_id, 'file', done
|
@path = "/file/system/path.png"
|
||||||
|
@entity = _id: @entity_id
|
||||||
|
@ProjectEntityHandler._cleanUpEntity @project, @entity, 'file', @path, userId, done
|
||||||
|
|
||||||
it "should delete the file from FileStoreHandler", ->
|
it "should delete the file from FileStoreHandler", ->
|
||||||
@FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal true
|
@FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal true
|
||||||
|
@ -201,38 +203,56 @@ describe 'ProjectEntityHandler', ->
|
||||||
it "should not attempt to delete from the document updater", ->
|
it "should not attempt to delete from the document updater", ->
|
||||||
@documentUpdaterHandler.deleteDoc.called.should.equal false
|
@documentUpdaterHandler.deleteDoc.called.should.equal false
|
||||||
|
|
||||||
|
it "should should send the update to the doc updater", ->
|
||||||
|
oldFiles = [ file: @entity, path: @path ]
|
||||||
|
@documentUpdaterHandler.updateProjectStructure
|
||||||
|
.calledWith(project_id, userId, {oldFiles})
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
describe "a doc", ->
|
describe "a doc", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@ProjectEntityHandler._cleanUpDoc = sinon.stub().callsArg(2)
|
@path = "/file/system/path.tex"
|
||||||
@ProjectEntityHandler._cleanUpEntity @project, @entity = {_id: @entity_id}, 'doc', done
|
@ProjectEntityHandler._cleanUpDoc = sinon.stub().callsArg(4)
|
||||||
|
@entity = {_id: @entity_id}
|
||||||
|
@ProjectEntityHandler._cleanUpEntity @project, @entity, 'doc', @path, userId, done
|
||||||
|
|
||||||
it "should clean up the doc", ->
|
it "should clean up the doc", ->
|
||||||
@ProjectEntityHandler._cleanUpDoc
|
@ProjectEntityHandler._cleanUpDoc
|
||||||
.calledWith(@project, @entity)
|
.calledWith(@project, @entity, @path, userId)
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
describe "a folder", ->
|
describe "a folder", ->
|
||||||
beforeEach (done) ->
|
beforeEach (done) ->
|
||||||
@folder =
|
@folder =
|
||||||
folders: [
|
folders: [
|
||||||
fileRefs: [ @file1 = {_id: "file-id-1" } ]
|
name: "subfolder"
|
||||||
docs: [ @doc1 = { _id: "doc-id-1" } ]
|
fileRefs: [ @file1 = { _id: "file-id-1", name: "file-name-1"} ]
|
||||||
|
docs: [ @doc1 = { _id: "doc-id-1", name: "doc-name-1" } ]
|
||||||
folders: []
|
folders: []
|
||||||
]
|
]
|
||||||
fileRefs: [ @file2 = { _id: "file-id-2" } ]
|
fileRefs: [ @file2 = { _id: "file-id-2", name: "file-name-2" } ]
|
||||||
docs: [ @doc2 = { _id: "doc-id-2" } ]
|
docs: [ @doc2 = { _id: "doc-id-2", name: "doc-name-2" } ]
|
||||||
|
|
||||||
@ProjectEntityHandler._cleanUpDoc = sinon.stub().callsArg(2)
|
@ProjectEntityHandler._cleanUpDoc = sinon.stub().callsArg(4)
|
||||||
@ProjectEntityHandler._cleanUpFile = sinon.stub().callsArg(2)
|
@ProjectEntityHandler._cleanUpFile = sinon.stub().callsArg(4)
|
||||||
@ProjectEntityHandler._cleanUpEntity @project, @folder, "folder", done
|
path = "/folder"
|
||||||
|
@ProjectEntityHandler._cleanUpEntity @project, @folder, "folder", path, userId, done
|
||||||
|
|
||||||
it "should clean up all sub files", ->
|
it "should clean up all sub files", ->
|
||||||
@ProjectEntityHandler._cleanUpFile.calledWith(@project, @file1).should.equal true
|
@ProjectEntityHandler._cleanUpFile
|
||||||
@ProjectEntityHandler._cleanUpFile.calledWith(@project, @file2).should.equal true
|
.calledWith(@project, @file1, "/folder/subfolder/file-name-1", userId)
|
||||||
|
.should.equal true
|
||||||
|
@ProjectEntityHandler._cleanUpFile
|
||||||
|
.calledWith(@project, @file2, "/folder/file-name-2", userId)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should clean up all sub docs", ->
|
it "should clean up all sub docs", ->
|
||||||
@ProjectEntityHandler._cleanUpDoc.calledWith(@project, @doc1).should.equal true
|
@ProjectEntityHandler._cleanUpDoc
|
||||||
@ProjectEntityHandler._cleanUpDoc.calledWith(@project, @doc2).should.equal true
|
.calledWith(@project, @doc1, "/folder/subfolder/doc-name-1", userId)
|
||||||
|
.should.equal true
|
||||||
|
@ProjectEntityHandler._cleanUpDoc
|
||||||
|
.calledWith(@project, @doc2, "/folder/doc-name-2", userId)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
describe 'moveEntity', ->
|
describe 'moveEntity', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@ -1116,6 +1136,7 @@ describe 'ProjectEntityHandler', ->
|
||||||
@doc =
|
@doc =
|
||||||
_id: ObjectId()
|
_id: ObjectId()
|
||||||
name: "test.tex"
|
name: "test.tex"
|
||||||
|
@path = "/path/to/doc"
|
||||||
@ProjectEntityHandler.unsetRootDoc = sinon.stub().callsArg(1)
|
@ProjectEntityHandler.unsetRootDoc = sinon.stub().callsArg(1)
|
||||||
@ProjectEntityHandler._insertDeletedDocReference = sinon.stub().callsArg(2)
|
@ProjectEntityHandler._insertDeletedDocReference = sinon.stub().callsArg(2)
|
||||||
@documentUpdaterHandler.deleteDoc = sinon.stub().callsArg(2)
|
@documentUpdaterHandler.deleteDoc = sinon.stub().callsArg(2)
|
||||||
|
@ -1125,7 +1146,7 @@ describe 'ProjectEntityHandler', ->
|
||||||
describe "when the doc is the root doc", ->
|
describe "when the doc is the root doc", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@project.rootDoc_id = @doc._id
|
@project.rootDoc_id = @doc._id
|
||||||
@ProjectEntityHandler._cleanUpDoc @project, @doc, @callback
|
@ProjectEntityHandler._cleanUpDoc @project, @doc, @path, userId, @callback
|
||||||
|
|
||||||
it "should unset the root doc", ->
|
it "should unset the root doc", ->
|
||||||
@ProjectEntityHandler.unsetRootDoc
|
@ProjectEntityHandler.unsetRootDoc
|
||||||
|
@ -1146,13 +1167,19 @@ describe 'ProjectEntityHandler', ->
|
||||||
.calledWith(project_id, @doc._id.toString())
|
.calledWith(project_id, @doc._id.toString())
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
it "should should send the update to the doc updater", ->
|
||||||
|
oldDocs = [ doc: @doc, path: @path ]
|
||||||
|
@documentUpdaterHandler.updateProjectStructure
|
||||||
|
.calledWith(project_id, userId, {oldDocs})
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
it "should call the callback", ->
|
it "should call the callback", ->
|
||||||
@callback.called.should.equal true
|
@callback.called.should.equal true
|
||||||
|
|
||||||
describe "when the doc is not the root doc", ->
|
describe "when the doc is not the root doc", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@project.rootDoc_id = ObjectId()
|
@project.rootDoc_id = ObjectId()
|
||||||
@ProjectEntityHandler._cleanUpDoc @project, @doc, @callback
|
@ProjectEntityHandler._cleanUpDoc @project, @doc, @path, userId, @callback
|
||||||
|
|
||||||
it "should not unset the root doc", ->
|
it "should not unset the root doc", ->
|
||||||
@ProjectEntityHandler.unsetRootDoc.called.should.equal false
|
@ProjectEntityHandler.unsetRootDoc.called.should.equal false
|
||||||
|
|
|
@ -8,7 +8,7 @@ describe 'TpdsUpdateHandler', ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
@requestQueuer = {}
|
@requestQueuer = {}
|
||||||
@updateMerger =
|
@updateMerger =
|
||||||
deleteUpdate: (user_id, path, source, cb)->cb()
|
deleteUpdate: (user_id, project_id, path, source, cb)->cb()
|
||||||
mergeUpdate:(user_id, project_id, path, update, source, cb)->cb()
|
mergeUpdate:(user_id, project_id, path, update, source, cb)->cb()
|
||||||
@editorController = {}
|
@editorController = {}
|
||||||
@project_id = "dsjajilknaksdn"
|
@project_id = "dsjajilknaksdn"
|
||||||
|
@ -107,11 +107,13 @@ describe 'TpdsUpdateHandler', ->
|
||||||
it 'should call deleteEntity in the collaberation manager', (done)->
|
it 'should call deleteEntity in the collaberation manager', (done)->
|
||||||
path = "/delete/this"
|
path = "/delete/this"
|
||||||
update = {}
|
update = {}
|
||||||
@updateMerger.deleteUpdate = sinon.stub().callsArg(3)
|
@updateMerger.deleteUpdate = sinon.stub().callsArg(4)
|
||||||
|
|
||||||
@handler.deleteUpdate @user_id, @project.name, path, @source, =>
|
@handler.deleteUpdate @user_id, @project.name, path, @source, =>
|
||||||
@projectDeleter.markAsDeletedByExternalSource.calledWith(@project._id).should.equal false
|
@projectDeleter.markAsDeletedByExternalSource.calledWith(@project._id).should.equal false
|
||||||
@updateMerger.deleteUpdate.calledWith(@project_id, path, @source).should.equal true
|
@updateMerger.deleteUpdate
|
||||||
|
.calledWith(@user_id, @project_id, path, @source)
|
||||||
|
.should.equal true
|
||||||
done()
|
done()
|
||||||
|
|
||||||
it 'should mark the project as deleted by external source if path is a single slash', (done)->
|
it 'should mark the project as deleted by external source if path is a single slash', (done)->
|
||||||
|
|
|
@ -145,13 +145,13 @@ describe 'UpdateMerger :', ->
|
||||||
|
|
||||||
it 'should get the element id', ->
|
it 'should get the element id', ->
|
||||||
@projectLocator.findElementByPath = sinon.spy()
|
@projectLocator.findElementByPath = sinon.spy()
|
||||||
@updateMerger.deleteUpdate @project_id, @path, @source, ->
|
@updateMerger.deleteUpdate @user_id, @project_id, @path, @source, ->
|
||||||
@projectLocator.findElementByPath.calledWith(@project_id, @path).should.equal true
|
@projectLocator.findElementByPath.calledWith(@project_id, @path).should.equal true
|
||||||
|
|
||||||
it 'should delete the entity in the editor controller with the correct type', (done)->
|
it 'should delete the entity in the editor controller with the correct type', (done)->
|
||||||
@entity.lines = []
|
@entity.lines = []
|
||||||
mock = sinon.mock(@editorController).expects("deleteEntity").withArgs(@project_id, @entity_id, @type, @source).callsArg(4)
|
mock = sinon.mock(@editorController).expects("deleteEntity").withArgs(@project_id, @entity_id, @type, @source, @user_id).callsArg(5)
|
||||||
@updateMerger.deleteUpdate @project_id, @path, @source, ->
|
@updateMerger.deleteUpdate @user_id, @project_id, @path, @source, ->
|
||||||
mock.verify()
|
mock.verify()
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue