Example #1
0
 def test_storm_object_modified_no_edited_fields(self):
     # A longpoll event is not emitted unless edited_fields is populated.
     storm_object = FakeStormClass()
     storm_object.id = 1234
     with capture_longpoll_emissions() as log:
         notify(ObjectModifiedEvent(storm_object, storm_object, None))
     self.assertEqual([], log)
     with capture_longpoll_emissions() as log:
         notify(ObjectModifiedEvent(storm_object, storm_object, ()))
     self.assertEqual([], log)
Example #2
0
    def execute(self, context, current_event):
        """See IEmailCommand."""
        if isinstance(context, CreateBugParams):
            # No one intentially reports a duplicate bug. Bug email commands
            # support CreateBugParams, so in this case, just return.
            return context, current_event
        self._ensureNumberOfArguments()
        [bug_id] = self.string_args

        if bug_id != 'no':
            try:
                bug = getUtility(IBugSet).getByNameOrID(bug_id)
            except NotFoundError:
                raise EmailProcessingError(
                    get_error_message('no-such-bug.txt',
                                      error_templates=error_templates,
                                      bug_id=bug_id))
        else:
            # 'no' is a special value for unmarking a bug as a duplicate.
            bug = None

        duplicate_field = IBug['duplicateof'].bind(context)
        try:
            duplicate_field.validate(bug)
        except ValidationError as error:
            raise EmailProcessingError(error.doc())

        context_snapshot = Snapshot(context, providing=providedBy(context))
        context.markAsDuplicate(bug)
        current_event = ObjectModifiedEvent(context, context_snapshot,
                                            'duplicateof')
        notify(current_event)
        return bug, current_event
Example #3
0
    def request_action(self, action, data):
        changed = False
        recipe_before_modification = Snapshot(self.context,
                                              providing=providedBy(
                                                  self.context))

        recipe_text = data.pop('recipe_text')
        parser = RecipeParser(recipe_text)
        recipe = parser.parse()
        if self.context.builder_recipe != recipe:
            try:
                self.error_handler(self.context.setRecipeText, recipe_text)
                changed = True
            except ErrorHandled:
                return

        distros = data.pop('distroseries')
        if distros != self.context.distroseries:
            self.context.distroseries.clear()
            for distroseries_item in distros:
                self.context.distroseries.add(distroseries_item)
            changed = True

        if self.updateContextFromData(data, notify_modified=False):
            changed = True

        if changed:
            field_names = [
                form_field.__name__ for form_field in self.form_fields
            ]
            notify(
                ObjectModifiedEvent(self.context, recipe_before_modification,
                                    field_names))

        self.next_url = canonical_url(self.context)
    def updateContextFromData(self, data, context=None, notify_modified=True):
        """Update the context object based on form data.

        If no context is given, the view's context is used.

        If any changes were made, ObjectModifiedEvent will be
        emitted.

        This method should be called by an action method of the form.

        Returns True if there were any changes to apply.
        """
        if context is None:
            context = self.context
        if notify_modified:
            context_before_modification = Snapshot(
                context, providing=providedBy(context))

        was_changed = form.applyChanges(context, self.form_fields, data,
                                        self.adapters)
        if was_changed and notify_modified:
            field_names = [
                form_field.__name__ for form_field in self.form_fields
            ]
            notify(
                ObjectModifiedEvent(context, context_before_modification,
                                    field_names))
        return was_changed
Example #5
0
    def test_workitems_added_notification_message(self):
        """ Test that we get a notification for setting work items on a new
        specification."""
        stub.test_emails = []
        spec = self.factory.makeSpecification()
        old_spec = Snapshot(spec, providing=providedBy(spec))
        new_work_item = {
            'title': u'A work item',
            'status': SpecificationWorkItemStatus.TODO,
            'assignee': None,
            'milestone': None,
            'sequence': 0
        }

        login_person(spec.owner)
        spec.updateWorkItems([new_work_item])
        # For API requests, lazr.restful does the notify() call, for this test
        # we need to call ourselves.
        transaction.commit()
        notify(ObjectModifiedEvent(
            spec, old_spec, edited_fields=['workitems_text']))
        transaction.commit()

        self.assertEqual(1, len(stub.test_emails))
        rationale = 'Work items set to:\nWork items:\n%s: %s' % (
            new_work_item['title'],
            new_work_item['status'].name)
        [email] = stub.test_emails
        # Actual message is part 2 of the e-mail.
        msg = email[2]
        self.assertIn(rationale, msg)
Example #6
0
 def linkBug(self, action, data):
     """Link to the requested bug. Publish an ObjectModifiedEvent and
     display a notification.
     """
     response = self.request.response
     target_unmodified = Snapshot(self.context,
                                  providing=providedBy(self.context))
     bug = data['bug']
     try:
         self.context.linkBug(bug, user=self.user)
     except Unauthorized:
         # XXX flacoste 2006-08-23 bug=57470: This should use proper _().
         self.setFieldError(
             'bug',
             'You are not allowed to link to private bug #%d.' % bug.id)
         return
     bug_props = {'bugid': bug.id, 'title': bug.title}
     response.addNotification(
         _(
             u'Added link to bug #$bugid: '
             u'\N{left double quotation mark}$title'
             u'\N{right double quotation mark}.',
             mapping=bug_props))
     notify(ObjectModifiedEvent(self.context, target_unmodified, ['bugs']))
     self.next_url = canonical_url(self.context)
Example #7
0
 def remove_action(self, action, data):
     """Update the bug."""
     bug = self.context.bug
     bug_before_modification = Snapshot(bug, providing=providedBy(bug))
     bug.markAsDuplicate(None)
     notify(
         ObjectModifiedEvent(bug, bug_before_modification, 'duplicateof'))
     return self._duplicate_action_result()
Example #8
0
 def test_no_job_created_if_no_delta(self):
     """Ensure None is returned if no change has been made."""
     merge_proposal, person = self.makeProposalWithSubscriber()
     old_merge_proposal = Snapshot(
         merge_proposal, providing=providedBy(merge_proposal))
     event = ObjectModifiedEvent(
         merge_proposal, old_merge_proposal, [], merge_proposal.registrant)
     merge_proposal_modified(merge_proposal, event)
     self.assertIs(None, self.getProposalUpdatedEmailJob(merge_proposal))
Example #9
0
 def test_no_job_created_if_work_in_progress(self):
     """Ensure None is returned if no change has been made."""
     merge_proposal, person = self.makeProposalWithSubscriber(
         needs_review=False)
     old_merge_proposal = Snapshot(
         merge_proposal, providing=providedBy(merge_proposal))
     merge_proposal.commit_message = 'new commit message'
     merge_proposal.description = 'change description'
     event = ObjectModifiedEvent(
         merge_proposal, old_merge_proposal, [], merge_proposal.registrant)
     merge_proposal_modified(merge_proposal, event)
     self.assertIs(None, self.getProposalUpdatedEmailJob(merge_proposal))
Example #10
0
 def test_job_created_if_work_in_progress_merged(self):
     # If work in progress is merged, then that is email worthy.
     merge_proposal, person = self.makeProposalWithSubscriber(
         needs_review=False)
     old_merge_proposal = Snapshot(
         merge_proposal, providing=providedBy(merge_proposal))
     merge_proposal.setStatus(BranchMergeProposalStatus.MERGED)
     event = ObjectModifiedEvent(
         merge_proposal, old_merge_proposal, [], merge_proposal.registrant)
     merge_proposal_modified(merge_proposal, event)
     job = self.getProposalUpdatedEmailJob(merge_proposal)
     self.assertIsNot(None, job, 'Job was not created.')
 def test_what_changed_works_with_fieldnames(self):
     # When what_changed is passed an ObjectModifiedEvent with a list
     # of fieldnames in its edited_fields property, it will deal with
     # those fields appropriately.
     bug = self.factory.makeBug()
     bug_before_modification = Snapshot(bug, providing=IBug)
     with person_logged_in(bug.owner):
         bug.setPrivate(True, bug.owner)
     event = ObjectModifiedEvent(bug, bug_before_modification, ['private'])
     expected_changes = {'private': ['False', 'True']}
     changes = what_changed(event)
     self.assertEqual(expected_changes, changes)
 def test_duplicate_edit_notifications(self):
     # Bug edits for a duplicate are sent to duplicate subscribers only.
     bug_before_modification = Snapshot(self.dupe_bug,
                                        providing=providedBy(self.dupe_bug))
     self.dupe_bug.description = 'A changed description'
     notify(
         ObjectModifiedEvent(self.dupe_bug,
                             bug_before_modification, ['description'],
                             user=self.dupe_bug.owner))
     latest_notification = BugNotification.selectFirst(orderBy='-id')
     recipients = set(recipient.person
                      for recipient in latest_notification.recipients)
     self.assertEqual(self.dupe_subscribers, recipients)
 def test_what_changed_works_with_field_instances(self):
     # Sometimes something will pass what_changed an
     # ObjectModifiedEvent where the edited_fields list contains
     # field instances. what_changed handles that correctly, too.
     bug = self.factory.makeBug()
     bug_before_modification = Snapshot(bug, providing=IBug)
     with person_logged_in(bug.owner):
         bug.setPrivate(True, bug.owner)
     event = ObjectModifiedEvent(bug, bug_before_modification,
                                 [IBug['private']])
     expected_changes = {'private': ['False', 'True']}
     changes = what_changed(event)
     self.assertEqual(expected_changes, changes)
Example #14
0
    def test_modifyPOTemplate_makes_job(self):
        """Creating a Packaging should make a TranslationMergeJob."""
        potemplate = self.factory.makePOTemplate()
        finder = JobFinder(None, None, TranslationTemplateChangeJob,
                           potemplate)
        self.assertEqual([], finder.find())
        with person_logged_in(potemplate.owner):
            snapshot = Snapshot(potemplate, providing=IPOTemplate)
            potemplate.name = self.factory.getUniqueString()
            notify(ObjectModifiedEvent(potemplate, snapshot, ["name"]))

        (job, ) = finder.find()
        self.assertIsInstance(job, TranslationTemplateChangeJob)
Example #15
0
 def test_no_job_created_if_only_preview_diff_changed(self):
     """Ensure None is returned if only the preview diff has changed."""
     merge_proposal, person = self.makeProposalWithSubscriber()
     old_merge_proposal = Snapshot(
         merge_proposal, providing=providedBy(merge_proposal))
     merge_proposal.updatePreviewDiff(
         ''.join(unified_diff('', 'Fake diff')),
         unicode(self.factory.getUniqueString('revid')),
         unicode(self.factory.getUniqueString('revid')))
     event = ObjectModifiedEvent(
         merge_proposal, old_merge_proposal, [], merge_proposal.registrant)
     merge_proposal_modified(merge_proposal, event)
     self.assertIs(None, self.getProposalUpdatedEmailJob(merge_proposal))
Example #16
0
    def expireBugTasks(self, transaction_manager):
        """Expire old, unassigned, Incomplete BugTasks.

        Only BugTasks for projects that use Malone are updated. This method
        will login as the bug_watch_updater celebrity and logout after the
        expiration is done.
        """
        message_template = ('[Expired for %s because there has been no '
                            'activity for %d days.]')
        self.log.info('Expiring unattended, INCOMPLETE bugtasks older than '
                      '%d days for projects that use Launchpad Bugs.' %
                      self.days_before_expiration)
        self._login()
        try:
            expired_count = 0
            bugtask_set = getUtility(IBugTaskSet)
            incomplete_bugtasks = bugtask_set.findExpirableBugTasks(
                self.days_before_expiration,
                user=self.janitor,
                target=self.target,
                limit=self.limit)
            self.log.info('Found %d bugtasks to expire.' %
                          incomplete_bugtasks.count())
            for bugtask in incomplete_bugtasks:
                # We don't expire bugtasks with conjoined masters.
                if bugtask.conjoined_master:
                    continue

                bugtask_before_modification = Snapshot(
                    bugtask, providing=providedBy(bugtask))
                bugtask.transitionToStatus(BugTaskStatus.EXPIRED, self.janitor)
                content = message_template % (bugtask.bugtargetdisplayname,
                                              self.days_before_expiration)
                bugtask.bug.newMessage(owner=self.janitor,
                                       subject=bugtask.bug.followup_subject(),
                                       content=content)
                notify(
                    ObjectModifiedEvent(bugtask,
                                        bugtask_before_modification,
                                        ['status'],
                                        user=self.janitor))
                # We commit after each expiration because emails are sent
                # immediately in zopeless. This minimize the risk of
                # duplicate expiration emails being sent in case an error
                # occurs later on.
                transaction_manager.commit()
                expired_count += 1
            self.log.info('Expired %d bugtasks.' % expired_count)
        finally:
            self._logout()
        self.log.info('Finished expiration run.')
Example #17
0
 def test_assignee_not_a_subscriber(self):
     """Test that a new recipient being assigned a bug task does send
        a NEW message."""
     self.assertEqual(len(stub.test_emails), 0, 'emails in queue')
     self.bug_task.transitionToAssignee(self.person_assigned)
     notify(ObjectModifiedEvent(
         self.bug_task, self.bug_task_before_modification,
         ['assignee'], user=self.user))
     transaction.commit()
     self.assertEqual(len(stub.test_emails), 1, 'email not sent')
     new_message = '[NEW]'
     msg = stub.test_emails[-1][2]
     self.assertTrue(new_message in msg,
                     '%s not in \n%s\n' % (new_message, msg))
def notify_modified(proposal, func, *args, **kwargs):
    """Call func, then notify about the changes it made.

    :param proposal: the merge proposal to notify about.
    :param func: The callable that will modify the merge proposal.
    :param args: Additional arguments for the method.
    :param kwargs: Keyword arguments for the method.
    :return: The return value of the method.
    """
    from lp.code.adapters.branch import BranchMergeProposalNoPreviewDiffDelta
    snapshot = BranchMergeProposalNoPreviewDiffDelta.snapshot(proposal)
    result = func(*args, **kwargs)
    notify(ObjectModifiedEvent(proposal, snapshot, []))
    return result
Example #19
0
 def change_action(self, action, data):
     """Update the bug."""
     data = dict(data)
     # We handle duplicate changes by hand instead of leaving it to
     # the usual machinery because we must use bug.markAsDuplicate().
     bug = self.context.bug
     bug_before_modification = Snapshot(bug, providing=providedBy(bug))
     duplicateof = data.pop('duplicateof')
     bug.markAsDuplicate(duplicateof)
     notify(
         ObjectModifiedEvent(bug, bug_before_modification, 'duplicateof'))
     # Apply other changes.
     self.updateBugFromData(data)
     return self._duplicate_action_result()
 def test_notifications_for_question_subscribers(self):
     # Ensure that notifications are sent to subscribers of a
     # question linked to the expired bug.
     bugtask = self.bug.default_bugtask
     bugtask_before_modification = Snapshot(bugtask, providing=IBugTask)
     bugtask.transitionToStatus(BugTaskStatus.EXPIRED, self.product.owner)
     bug_modified = ObjectModifiedEvent(bugtask,
                                        bugtask_before_modification,
                                        ["status"])
     notify(bug_modified)
     recipients = [
         job.metadata['recipient_set'] for job in pop_questionemailjobs()
     ]
     self.assertContentEqual(['ASKER_SUBSCRIBER'], recipients)
Example #21
0
 def makeProposalUpdatedEmailJob(self):
     """Fixture method providing a mailer for a modified merge proposal"""
     merge_proposal, subscriber = self.makeProposalWithSubscriber()
     old_merge_proposal = Snapshot(
         merge_proposal, providing=providedBy(merge_proposal))
     merge_proposal.requestReview()
     merge_proposal.commit_message = 'new commit message'
     merge_proposal.description = 'change description'
     event = ObjectModifiedEvent(
         merge_proposal, old_merge_proposal, [], merge_proposal.registrant)
     merge_proposal_modified(merge_proposal, event)
     job = self.getProposalUpdatedEmailJob(merge_proposal)
     self.assertIsNot(None, job, 'Job was not created.')
     return job, subscriber
Example #22
0
 def test_iterReady_supports_updated_emails(self):
     # iterReady will also return pending MergeProposalUpdatedEmailJob.
     bmp = self.makeBranchMergeProposal(
         set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
     self.completePendingJobs()
     old_merge_proposal = (
         BranchMergeProposalNoPreviewDiffDelta.snapshot(bmp))
     bmp.commit_message = 'new commit message'
     event = ObjectModifiedEvent(
         bmp, old_merge_proposal, [], bmp.registrant)
     merge_proposal_modified(bmp, event)
     [job] = self.job_source.iterReady()
     self.assertEqual(job.branch_merge_proposal, bmp)
     self.assertIsInstance(job, MergeProposalUpdatedEmailJob)
Example #23
0
    def execute(self, context, current_event):
        """See `IEmailCommand`.

        Much of this method was lifted from
        `EditEmailCommand.execute`.
        """
        # Parse args.
        self._ensureNumberOfArguments()
        [security_flag] = self.string_args
        if security_flag == 'yes':
            security_related = True
        elif security_flag == 'no':
            security_related = False
        else:
            raise EmailProcessingError(get_error_message(
                'security-parameter-mismatch.txt',
                error_templates=error_templates),
                                       stop_processing=True)

        if isinstance(context, CreateBugParams):
            if security_related:
                context.information_type = InformationType.PRIVATESECURITY
            return context, current_event

        # Take a snapshot.
        edited = False
        edited_fields = set()
        if IObjectModifiedEvent.providedBy(current_event):
            context_snapshot = current_event.object_before_modification
            edited_fields.update(current_event.edited_fields)
        else:
            context_snapshot = Snapshot(context, providing=providedBy(context))

        # Apply requested changes.
        user = getUtility(ILaunchBag).user
        if security_related:
            if context.setPrivate(True, user):
                edited = True
                edited_fields.add('private')
        if context.security_related != security_related:
            context.setSecurityRelated(security_related, user)
            edited = True
            edited_fields.add('security_related')

        # Update the current event.
        if edited and not IObjectCreatedEvent.providedBy(current_event):
            current_event = ObjectModifiedEvent(context, context_snapshot,
                                                list(edited_fields))

        return context, current_event
 def test_for_bug_modifier_header(self):
     """Test X-Launchpad-Bug-Modifier appears when a bug is modified."""
     self.bug_task.transitionToStatus(BugTaskStatus.CONFIRMED, self.user)
     notify(ObjectModifiedEvent(
         self.bug_task, self.bug_task_before_modification,
         ['status'], user=self.user))
     transaction.commit()
     latest_notification = BugNotification.selectFirst(orderBy='-id')
     notifications, omitted, messages = construct_email_notifications(
         [latest_notification])
     self.assertEqual(len(notifications), 1,
                      'email notification not created')
     headers = [msg['X-Launchpad-Bug-Modifier'] for msg in messages]
     self.assertEqual(len(headers), len(messages))
Example #25
0
    def change_action(self, action, data):
        """Update the bug."""
        data = dict(data)
        bug = self.context.bug
        information_type = data.pop('information_type')
        changed_fields = ['information_type']
        # When the user first submits the form, validate change is True and
        # so we check that the bug does not become invisible. If the user
        # confirms they really want to make the change, validate change is
        # False and we process the change as normal.
        if self.request.is_ajax:
            validate_change = data.get('validate_change', False)
            if (validate_change and
                information_type in PRIVATE_INFORMATION_TYPES and
                self._bug_will_be_invisible(information_type)):
                self.request.response.setStatus(400, "Bug Visibility")
                return ''

        user_will_be_subscribed = (
            information_type in PRIVATE_INFORMATION_TYPES and
            bug.getSubscribersForPerson(self.user).is_empty())
        bug_before_modification = Snapshot(bug, providing=providedBy(bug))
        changed = bug.transitionToInformationType(
            information_type, self.user)
        if changed:
            self._handlePrivacyChanged(user_will_be_subscribed)
            notify(
                ObjectModifiedEvent(
                    bug, bug_before_modification, changed_fields,
                    user=self.user))
        if self.request.is_ajax:
            # Avoid circular imports
            from lp.bugs.browser.bugtask import (
                can_add_package_task_to_bug,
                can_add_project_task_to_bug,
            )
            if changed:
                result_data = self._getSubscriptionDetails()
                result_data['can_add_project_task'] = (
                    can_add_project_task_to_bug(bug))
                result_data['can_add_package_task'] = (
                    can_add_package_task_to_bug(bug))
                self.request.response.setHeader(
                    'content-type', 'application/json')
                return dumps(
                    result_data, cls=ResourceJSONEncoder,
                    media_type=EntryResource.JSON_TYPE)
            else:
                return ''
Example #26
0
def notify_modified(obj, edited_fields, snapshot_names=None, user=None):
    """A context manager that notifies about modifications to an object.

    Use this as follows::

        with notify_modified(obj, ["attr"]):
            obj.attr = value

    Or::

        edited_fields = set()
        with notify_modified(obj, edited_fields):
            if obj.attr != new_attr:
                obj.attr = new_attr
                edited_fields.add("attr")

    Or even::

        edited_fields = set()
        with notify_modified(obj, edited_fields) as previous_obj:
            do_something()
            if obj.attr != previous_obj.attr:
                edited_fields.add("attr")

    :param obj: The object being modified.
    :param edited_fields: An iterable of fields being modified.  This is not
        used until after the block wrapped by the context manager has
        finished executing, so you can safely pass a mutable object and add
        items to it from inside the block as you determine which fields are
        being modified.  A notification will only be sent if `edited_fields`
        is non-empty.
    :param snapshot_names: If not None, only snapshot these names.  This may
        be used if snapshotting some of the object's attributes is expensive
        in some contexts (and they can't be wrapped by `doNotSnapshot` for
        some reason).
    :param user: If not None, the user making these changes.  If None,
        defaults to the principal registered in the current interaction.
    """
    obj_before_modification = Snapshot(obj,
                                       names=snapshot_names,
                                       providing=providedBy(obj))
    yield obj_before_modification
    edited_fields = list(edited_fields)
    if edited_fields:
        notify(
            ObjectModifiedEvent(obj,
                                obj_before_modification,
                                edited_fields,
                                user=user))
Example #27
0
    def monitor(klass, merge_proposal):
        """Context manager to monitor for changes in a merge proposal.

        If the merge proposal has changed, an `ObjectModifiedEvent` is issued
        via `zope.event.notify`.
        """
        merge_proposal_snapshot = klass.snapshot(merge_proposal)
        yield
        merge_proposal_delta = klass.construct(merge_proposal_snapshot,
                                               merge_proposal)
        if merge_proposal_delta is not None:
            merge_proposal_event = ObjectModifiedEvent(
                merge_proposal, merge_proposal_snapshot,
                vars(merge_proposal_delta).keys())
            notify(merge_proposal_event)
Example #28
0
 def test_assignee_new_subscriber(self):
     """Build a list of people who will receive e-mails about the bug
     task changes and ensure the assignee is not one."""
     self.bug_task.transitionToAssignee(self.person_assigned)
     notify(ObjectModifiedEvent(
         self.bug_task, self.bug_task_before_modification,
         ['assignee'], user=self.user))
     latest_notification = BugNotification.selectFirst(orderBy='-id')
     notifications, omitted, messages = construct_email_notifications(
         [latest_notification])
     self.assertEqual(len(notifications), 1,
                      'email notication not created')
     receivers = [message['To'] for message in messages]
     self.assertFalse(self.person_assigned_email in receivers,
         'Assignee was emailed about the bug task change')
Example #29
0
 def test_assignee_notification_message(self):
     """Test notification string when a person is assigned a task by
        someone else."""
     self.assertEqual(len(stub.test_emails), 0, 'emails in queue')
     self.bug_task.transitionToAssignee(self.person_assigned)
     notify(ObjectModifiedEvent(
         self.bug_task, self.bug_task_before_modification,
         ['assignee'], user=self.user))
     transaction.commit()
     self.assertEqual(len(stub.test_emails), 1, 'email not sent')
     rationale = (
         'Sample Person (name12) has assigned this bug to you for Rebirth')
     msg = stub.test_emails[-1][2]
     self.assertTrue(rationale in msg,
                     '%s not in\n%s\n' % (rationale, msg))
Example #30
0
 def test_storm_object_modified(self):
     storm_object = FakeStormClass()
     storm_object.id = 1234
     with capture_longpoll_emissions() as log:
         object_event = ObjectModifiedEvent(storm_object, storm_object,
                                            ("itchy", "scratchy"))
         notify(object_event)
     expected = LongPollEventRecord(
         "longpoll.event.faketable.1234", {
             "event_key": "longpoll.event.faketable.1234",
             "what": "modified",
             "edited_fields": ["itchy", "scratchy"],
             "id": 1234,
         })
     self.assertEqual([expected], log)