notification.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import celery
  2. import json
  3. import pagure
  4. import rdflib
  5. from .. import activitypub
  6. from .. import model
  7. from .. import settings
  8. from . import broker
  9. from . import broker_url
  10. from . import database_session
  11. log = celery.utils.log.get_task_logger(__name__)
  12. log.setLevel(settings.LOG_LEVEL)
  13. # The following is a decorator that accepts a Pagure notification ID as input,
  14. # for example "issue.new", and adds it to _USER_ACTIONS_ together with the
  15. # decorated function. _USER_ACTIONS_ is a dictionary mapping Pagure notification
  16. # IDs to a function to be executed when a new notification is received.
  17. _USER_ACTIONS_ = {}
  18. def action(notification_id):
  19. def closure(func):
  20. def decorator(*args, **kwargs):
  21. func(*args, **kwargs)
  22. global _USER_ACTIONS_
  23. _USER_ACTIONS_[notification_id] = decorator
  24. return decorator
  25. return closure
  26. @broker.task
  27. def handle_pagure_signal(notification_id, message):
  28. """
  29. This task receives notifications from Pagure about events that happen on
  30. the instance, creates a new activity, and schedules their delivery.
  31. """
  32. if notification_id not in _USER_ACTIONS_:
  33. log.debug('Unhandled user action {}.'.format(notification_id))
  34. return
  35. log.debug('New Pagure notification: {}\n{}'.format(notification_id,
  36. json.dumps(message, indent=4, sort_keys=True)))
  37. with database_session() as database:
  38. # Handle the user action
  39. return _USER_ACTIONS_[notification_id](database, message)
  40. @action('issue.new')
  41. def new_issue(database, message):
  42. """
  43. A user has created a new Issue.
  44. """
  45. person = database \
  46. .query(model.Person) \
  47. .filter(model.Person.user == message['issue']['user']['name']) \
  48. .one_or_none()
  49. project = database \
  50. .query(model.Project) \
  51. .filter(model.Project.id == message['project']['id']) \
  52. .one_or_none()
  53. issue = database \
  54. .query(pagure.lib.model.Issue) \
  55. .filter(pagure.lib.model.Issue.id == message['issue']['id'],
  56. pagure.lib.model.Issue.project_id == message['project']['id']) \
  57. .one_or_none()
  58. # This should never raise an error otherwise there's a bug in Pagure
  59. assert person and issue and project, \
  60. "User, Issue, or Project doesn't exist."
  61. # Check if this tracker is only used to interact with a remote one
  62. same_as = database.query(database.SameAs) \
  63. .filter(database.SameAs.local_uri == project.local_uri) \
  64. .one_or_none()
  65. if same_as:
  66. log.debug('Sending new issue to remote tracker...')
  67. ticket = database \
  68. .query(model.Ticket) \
  69. .filter(model.Ticket.id == issue.id,
  70. model.Ticket.project_id == issue.project_id) \
  71. .one_or_none()
  72. # Offer a new ticket to the remote tracker
  73. # person.offer(ticket.jsonld, to=same_as.remote_uri)
  74. @action('issue.comment.added')
  75. def new_issue_comment(database, message):
  76. # A user has commented on an issue
  77. # The Pagure notification contains *all* the comments of the issue
  78. # in an ordered list, so we extract the last one from the list of
  79. # comments.
  80. comment = pagure_db \
  81. .query(model.TicketComment) \
  82. .filter(model.TicketComment.id == message['issue']['comments'][-1]['id']) \
  83. .one_or_none()
  84. person = pagure_db \
  85. .query(model.Person) \
  86. .filter(model.Person.id == comment.user.id) \
  87. .one_or_none()
  88. project = pagure_db \
  89. .query(model.Project) \
  90. .filter(model.Project.id == message['project']['id']) \
  91. .one_or_none()
  92. # Our local ticket
  93. ticket = pagure_db \
  94. .query(model.Ticket) \
  95. .filter(model.Ticket.id == message['issue']['id'],
  96. model.Ticket.project_id == message['project']['id']) \
  97. .one_or_none()
  98. # Check if this tracker is only used to interact with a remote one
  99. same_as = forgefed_db.query(database.SameAs) \
  100. .filter(database.SameAs.local_uri == project.local_uri) \
  101. .one_or_none()
  102. if same_as:
  103. log.debug('Sending new comment to remote tracker...')
  104. assert ticket.remote_uri, 'Ticket not linked to remote ticket.'
  105. # Notify the remote tracker about the new comment
  106. person.create(comment.jsonld, to=same_as.remote_uri)
  107. else:
  108. log.debug('Not a remote tracker. Will send new comment to tracker followers '
  109. + 'and watchers.')
  110. # Retrieve the pagure "watchlist"
  111. watchlist = pagure.lib.query.get_watch_list(pagure_db, ticket)
  112. actors = []
  113. for username in watchlist:
  114. actor = pagure_db \
  115. .query(model.Person) \
  116. .filter(model.Person.user == username) \
  117. .one_or_none()
  118. actors.append(actor.uri)
  119. # Send the Activity
  120. person.create(comment.jsonld, to=actors)
  121. @action('issue.edit')
  122. def edit_issue(database, message):
  123. if topic == 'issue.edit' \
  124. and 'status' in message['fields'] \
  125. and message['issue']['status'].upper() == 'CLOSED':
  126. # A user has closed an issue
  127. # The Ticket that was modified
  128. ticket = pagure_db \
  129. .query(model.Ticket) \
  130. .filter(model.Ticket.id == message['issue']['id'],
  131. model.Ticket.project_id == message['project']['id']) \
  132. .one_or_none()
  133. # The user that edited the Ticket
  134. person = pagure_db \
  135. .query(model.Person) \
  136. .filter(model.Person.user == message['agent']) \
  137. .one_or_none()
  138. project = pagure_db \
  139. .query(model.Project) \
  140. .filter(model.Project.id == message['project']['id']) \
  141. .one_or_none()
  142. # If the user has edited the Ticket of a remote tracker, we just
  143. # send the Activity to the tracker
  144. if ticket.is_remote:
  145. log.debug('Local user has closed the remote Ticket {}'.format(ticket.uri))
  146. person.resolve(ticket.uri, to=project.remote_uri)
  147. # otherwise we send the Activity to the Tickets' watchlist
  148. else:
  149. log.debug('Local user has closed the local Ticket {}'.format(ticket.uri))
  150. # Retrieve the pagure "watchlist"
  151. watchlist = pagure.lib.query.get_watch_list(pagure_db, ticket)
  152. actors = []
  153. for username in watchlist:
  154. actor = pagure_db \
  155. .query(model.Person) \
  156. .filter(model.Person.user == username) \
  157. .one_or_none()
  158. actors.append(actor.uri)
  159. # Send the Activity
  160. person.resolve(ticket.uri, to=actors)