Merge ~ines-almeida/launchpad:add-bug-webhooks/add-interfaces into launchpad:master

Proposed by Ines Almeida
Status: Merged
Approved by: Ines Almeida
Approved revision: ba52f95c33b98c3a1806cf3c3a68075944346594
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ines-almeida/launchpad:add-bug-webhooks/add-interfaces
Merge into: launchpad:master
Prerequisite: ~ines-almeida/launchpad:add-bug-webhooks/update-webhook-model
Diff against target: 845 lines (+285/-84)
15 files modified
lib/lp/bugs/interfaces/bugtarget.py (+4/-0)
lib/lp/registry/browser/distribution.py (+24/-12)
lib/lp/registry/browser/distributionsourcepackage.py (+15/-1)
lib/lp/registry/browser/product.py (+14/-1)
lib/lp/registry/configure.zcml (+4/-48)
lib/lp/registry/interfaces/distribution.py (+4/-1)
lib/lp/registry/interfaces/distributionsourcepackage.py (+21/-8)
lib/lp/registry/interfaces/product.py (+2/-1)
lib/lp/registry/model/distribution.py (+6/-0)
lib/lp/registry/model/distributionsourcepackage.py (+6/-0)
lib/lp/registry/model/product.py (+6/-0)
lib/lp/services/features/flags.py (+8/-0)
lib/lp/services/webhooks/interfaces.py (+2/-0)
lib/lp/services/webhooks/tests/test_browser.py (+120/-5)
lib/lp/services/webhooks/tests/test_webservice.py (+49/-7)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+442544@code.launchpad.net

Commit message

Add interfaces to add new webhooks for bugtask targets

Webhooks can now be added to a project, a distribution or a distribution source package.

Description of the change

This will add the user interfaces to add new webhooks for the new targets (to be used with the new `bug` and `bug:comment` events)

All interfaces are hidden under a new feature flag, so these pages won't be exposed until the feature flag is set.

This MP is based of another open MP: https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/442543

The webhooks are not hooked to anything in this MP - that will be handled in another MP

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/bugs/interfaces/bugtarget.py b/lib/lp/bugs/interfaces/bugtarget.py
2index 57bc0fe..540079f 100644
3--- a/lib/lp/bugs/interfaces/bugtarget.py
4+++ b/lib/lp/bugs/interfaces/bugtarget.py
5@@ -15,6 +15,7 @@ __all__ = [
6 "ISeriesBugTarget",
7 "BUG_POLICY_ALLOWED_TYPES",
8 "BUG_POLICY_DEFAULT_TYPES",
9+ "BUG_WEBHOOKS_FEATURE_FLAG",
10 ]
11
12
13@@ -156,6 +157,9 @@ BUG_POLICY_DEFAULT_TYPES = {
14 }
15
16
17+BUG_WEBHOOKS_FEATURE_FLAG = "bugs.webhooks.enabled"
18+
19+
20 @exported_as_webservice_entry(as_of="beta")
21 class IHasBugs(Interface):
22 """An entity which has a collection of bug tasks."""
23diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
24index 3fb8a36..b990f6f 100644
25--- a/lib/lp/registry/browser/distribution.py
26+++ b/lib/lp/registry/browser/distribution.py
27@@ -83,6 +83,7 @@ from lp.bugs.browser.structuralsubscription import (
28 StructuralSubscriptionTargetTraversalMixin,
29 expose_structural_subscription_data_to_js,
30 )
31+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
32 from lp.buildmaster.interfaces.processor import IProcessorSet
33 from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
34 from lp.registry.browser import RegistryEditFormView, add_subscribe_link
35@@ -141,6 +142,7 @@ from lp.services.webapp.authorization import check_permission
36 from lp.services.webapp.batching import BatchNavigator
37 from lp.services.webapp.breadcrumb import Breadcrumb
38 from lp.services.webapp.interfaces import ILaunchBag
39+from lp.services.webhooks.browser import WebhookTargetNavigationMixin
40 from lp.soyuz.browser.archive import EnableProcessorsMixin
41 from lp.soyuz.browser.packagesearch import PackageSearchViewBase
42 from lp.soyuz.enums import ArchivePurpose
43@@ -155,6 +157,7 @@ class DistributionNavigation(
44 StructuralSubscriptionTargetTraversalMixin,
45 PillarNavigationMixin,
46 TargetDefaultVCSNavigationMixin,
47+ WebhookTargetNavigationMixin,
48 ):
49
50 usedfor = IDistribution
51@@ -388,6 +391,18 @@ class DistributionNavigationMenu(NavigationMenu, DistributionLinksMixin):
52 usedfor = IDistribution
53 facet = "overview"
54
55+ links = (
56+ "edit",
57+ "admin",
58+ "pubconf",
59+ "subscribe_to_bug_mail",
60+ "edit_bug_mail",
61+ "sharing",
62+ "new_oci_project",
63+ "search_oci_project",
64+ "webhooks",
65+ )
66+
67 @enabled_with_permission("launchpad.Admin")
68 def admin(self):
69 text = "Administer"
70@@ -419,18 +434,14 @@ class DistributionNavigationMenu(NavigationMenu, DistributionLinksMixin):
71 link.enabled = not oci_projects.is_empty()
72 return link
73
74- @cachedproperty
75- def links(self):
76- return [
77- "edit",
78- "admin",
79- "pubconf",
80- "subscribe_to_bug_mail",
81- "edit_bug_mail",
82- "sharing",
83- "new_oci_project",
84- "search_oci_project",
85- ]
86+ @enabled_with_permission("launchpad.Edit")
87+ def webhooks(self):
88+ return Link(
89+ "+webhooks",
90+ "Manage webhooks",
91+ icon="edit",
92+ enabled=bool(getFeatureFlag(BUG_WEBHOOKS_FEATURE_FLAG)),
93+ )
94
95
96 class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin):
97@@ -623,6 +634,7 @@ class DistributionBugsMenu(PillarBugsMenu):
98 def links(self):
99 links = ["bugsupervisor", "cve", "filebug"]
100 add_subscribe_link(links)
101+ links.append("webhooks")
102 return links
103
104
105diff --git a/lib/lp/registry/browser/distributionsourcepackage.py b/lib/lp/registry/browser/distributionsourcepackage.py
106index a55097b..e0d960a 100644
107--- a/lib/lp/registry/browser/distributionsourcepackage.py
108+++ b/lib/lp/registry/browser/distributionsourcepackage.py
109@@ -42,6 +42,7 @@ from lp.bugs.browser.structuralsubscription import (
110 StructuralSubscriptionTargetTraversalMixin,
111 expose_structural_subscription_data_to_js,
112 )
113+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
114 from lp.bugs.interfaces.bugtask import BugTaskStatus
115 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
116 from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
117@@ -54,6 +55,7 @@ from lp.registry.interfaces.distributionsourcepackage import (
118 from lp.registry.interfaces.person import IPersonSet
119 from lp.registry.interfaces.series import SeriesStatus
120 from lp.services.database.decoratedresultset import DecoratedResultSet
121+from lp.services.features import getFeatureFlag
122 from lp.services.helpers import shortlist
123 from lp.services.propertycache import cachedproperty
124 from lp.services.webapp import (
125@@ -76,6 +78,7 @@ from lp.services.webapp.menu import (
126 )
127 from lp.services.webapp.publisher import LaunchpadView
128 from lp.services.webapp.sorting import sorted_dotted_numbers
129+from lp.services.webhooks.browser import WebhookTargetNavigationMixin
130 from lp.soyuz.browser.sourcepackagerelease import linkify_changelog
131 from lp.soyuz.interfaces.archive import IArchiveSet
132 from lp.soyuz.interfaces.distributionsourcepackagerelease import (
133@@ -170,6 +173,15 @@ class DistributionSourcePackageLinksMixin:
134 get_data = "?field.status=OPEN"
135 return Link(base_path + get_data, "Open Questions", site="answers")
136
137+ @enabled_with_permission("launchpad.Edit")
138+ def webhooks(self):
139+ return Link(
140+ "+webhooks",
141+ "Manage webhooks",
142+ icon="edit",
143+ enabled=bool(getFeatureFlag(BUG_WEBHOOKS_FEATURE_FLAG)),
144+ )
145+
146
147 class DistributionSourcePackageOverviewMenu(
148 ApplicationMenu, DistributionSourcePackageLinksMixin
149@@ -193,6 +205,7 @@ class DistributionSourcePackageBugsMenu(
150 def links(self):
151 links = ["filebug"]
152 add_subscribe_link(links)
153+ links.append("webhooks")
154 return links
155
156
157@@ -214,6 +227,7 @@ class DistributionSourcePackageNavigation(
158 QuestionTargetTraversalMixin,
159 TargetDefaultVCSNavigationMixin,
160 StructuralSubscriptionTargetTraversalMixin,
161+ WebhookTargetNavigationMixin,
162 ):
163
164 usedfor = IDistributionSourcePackage
165@@ -285,7 +299,7 @@ class DistributionSourcePackageActionMenu(
166 def links(self):
167 links = ["publishing_history", "change_log"]
168 add_subscribe_link(links)
169- links.append("edit")
170+ links.extend(["edit", "webhooks"])
171 return links
172
173 def publishing_history(self):
174diff --git a/lib/lp/registry/browser/product.py b/lib/lp/registry/browser/product.py
175index 56c03a3..907d7f1 100644
176--- a/lib/lp/registry/browser/product.py
177+++ b/lib/lp/registry/browser/product.py
178@@ -111,6 +111,7 @@ from lp.bugs.browser.structuralsubscription import (
179 StructuralSubscriptionTargetTraversalMixin,
180 expose_structural_subscription_data_to_js,
181 )
182+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
183 from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES
184 from lp.charms.browser.hascharmrecipes import HasCharmRecipesMenuMixin
185 from lp.code.browser.branchref import BranchRef
186@@ -190,6 +191,7 @@ from lp.services.webapp.interfaces import UnsafeFormGetSubmissionError
187 from lp.services.webapp.menu import NavigationMenu
188 from lp.services.webapp.url import urlsplit
189 from lp.services.webapp.vhosts import allvhosts
190+from lp.services.webhooks.browser import WebhookTargetNavigationMixin
191 from lp.services.worlddata.helpers import browser_languages
192 from lp.services.worlddata.interfaces.country import ICountry
193 from lp.snappy.browser.hassnaps import HasSnapsMenuMixin
194@@ -210,6 +212,7 @@ class ProductNavigation(
195 StructuralSubscriptionTargetTraversalMixin,
196 PillarNavigationMixin,
197 TargetDefaultVCSNavigationMixin,
198+ WebhookTargetNavigationMixin,
199 ):
200
201 usedfor = IProduct
202@@ -512,6 +515,15 @@ class ProductEditLinksMixin(StructuralSubscriptionMenuMixin):
203 ) and product.canAdministerOCIProjects(self.user)
204 return link
205
206+ @enabled_with_permission("launchpad.Edit")
207+ def webhooks(self):
208+ return Link(
209+ "+webhooks",
210+ "Manage webhooks",
211+ icon="edit",
212+ enabled=bool(getFeatureFlag(BUG_WEBHOOKS_FEATURE_FLAG)),
213+ )
214+
215
216 class IProductEditMenu(Interface):
217 """A marker interface for the 'Change details' navigation menu."""
218@@ -537,6 +549,7 @@ class ProductActionNavigationMenu(NavigationMenu, ProductEditLinksMixin):
219 "sharing",
220 "search_oci_project",
221 "new_oci_project",
222+ "webhooks",
223 ]
224 add_subscribe_link(links)
225 return links
226@@ -648,7 +661,7 @@ class ProductBugsMenu(PillarBugsMenu, ProductEditLinksMixin):
227 def links(self):
228 links = ["filebug", "bugsupervisor", "cve"]
229 add_subscribe_link(links)
230- links.append("configure_bugtracker")
231+ links.extend(["configure_bugtracker", "webhooks"])
232 return links
233
234
235diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml
236index 22178f0..b2dc87f 100644
237--- a/lib/lp/registry/configure.zcml
238+++ b/lib/lp/registry/configure.zcml
239@@ -550,48 +550,14 @@
240 <class
241 class="lp.registry.model.distributionsourcepackage.DistributionSourcePackage">
242 <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
243- <allow interface="lp.bugs.interfaces.bugtarget.IBugTarget"/>
244 <allow
245 interface="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"/>
246
247 <allow
248- attributes="
249- __eq__
250- __getitem__
251- __ne__
252- _getOfficialTagClause
253- binary_names
254- bug_count
255- bugtasks
256- current_publishing_records
257- currentrelease
258- delete
259- development_version
260- display_name
261- displayname
262- distribution
263- distro
264- drivers
265- findRelatedArchivePublications
266- findRelatedArchives
267- getBranches
268- getMergeProposals
269- getReleasesAndPublishingHistory
270- getUsedBugTagsWithOpenCounts
271- getVersion
272- get_distroseries_packages
273- is_official
274- latest_overall_publication
275- name
276- official_bug_tags
277- personHasDriverRights
278- publishing_history
279- releases
280- sourcepackagename
281- subscribers
282- summary
283- title
284- upstream_product"/>
285+ interface="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackageView"/>
286+ <require
287+ permission="launchpad.Edit"
288+ interface="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackageEdit"/>
289
290 <!-- IStructuralSubscriptionTarget -->
291
292@@ -615,16 +581,6 @@
293 bug_reporting_guidelines
294 enable_bugfiling_duplicate_search
295 "/>
296-
297- <!-- IHasGitRepositories -->
298-
299- <allow
300- interface="lp.code.interfaces.hasgitrepositories.IHasGitRepositories" />
301-
302- <!-- IHasCodeImports -->
303-
304- <allow
305- interface="lp.code.interfaces.hasbranches.IHasCodeImports" />
306 </class>
307 <adapter
308 provides="lp.registry.interfaces.distribution.IDistribution"
309diff --git a/lib/lp/registry/interfaces/distribution.py b/lib/lp/registry/interfaces/distribution.py
310index 7fdc5ff..ef966c7 100644
311--- a/lib/lp/registry/interfaces/distribution.py
312+++ b/lib/lp/registry/interfaces/distribution.py
313@@ -102,6 +102,7 @@ from lp.services.fields import (
314 Summary,
315 Title,
316 )
317+from lp.services.webhooks.interfaces import IWebhookTarget
318 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
319 from lp.translations.interfaces.hastranslationimports import (
320 IHasTranslationImports,
321@@ -1064,7 +1065,9 @@ class IDistributionView(
322 """Return the vulnerability in this distribution with the given id."""
323
324
325-class IDistributionEditRestricted(IOfficialBugTagTargetRestricted):
326+class IDistributionEditRestricted(
327+ IOfficialBugTagTargetRestricted, IWebhookTarget
328+):
329 """IDistribution properties requiring launchpad.Edit permission."""
330
331 @mutator_for(IDistributionView["bug_sharing_policy"])
332diff --git a/lib/lp/registry/interfaces/distributionsourcepackage.py b/lib/lp/registry/interfaces/distributionsourcepackage.py
333index cba5873..1f116fc 100644
334--- a/lib/lp/registry/interfaces/distributionsourcepackage.py
335+++ b/lib/lp/registry/interfaces/distributionsourcepackage.py
336@@ -27,27 +27,22 @@ from lp.code.interfaces.hasbranches import (
337 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
338 from lp.registry.interfaces.distribution import IDistribution
339 from lp.registry.interfaces.role import IHasDrivers
340+from lp.services.webhooks.interfaces import IWebhookTarget
341 from lp.soyuz.enums import ArchivePurpose
342
343
344 @exported_as_webservice_entry(as_of="beta")
345-class IDistributionSourcePackage(
346+class IDistributionSourcePackageView(
347 IHeadingContext,
348 IBugTarget,
349 IHasBranches,
350 IHasMergeProposals,
351 IHasOfficialBugTags,
352- IStructuralSubscriptionTarget,
353- IQuestionTarget,
354 IHasDrivers,
355 IHasGitRepositories,
356 IHasCodeImports,
357 ):
358- """Represents a source package in a distribution.
359-
360- Create IDistributionSourcePackages by invoking
361- `IDistribution.getSourcePackage()`.
362- """
363+ """`IDistributionSourcePackage` attributes that require launchpad.View."""
364
365 distribution = exported(
366 Reference(IDistribution, title=_("The distribution."))
367@@ -209,3 +204,21 @@ class IDistributionSourcePackage(
368
369 :return: True if a persistent object was removed, otherwise False.
370 """
371+
372+
373+class IDistributionSourcePackageEdit(IWebhookTarget):
374+ """`IDistributionSourcePackage` attributes that require launchpad.Edit."""
375+
376+
377+@exported_as_webservice_entry(as_of="beta")
378+class IDistributionSourcePackage(
379+ IDistributionSourcePackageView,
380+ IDistributionSourcePackageEdit,
381+ IStructuralSubscriptionTarget,
382+ IQuestionTarget,
383+):
384+ """Represents a source package in a distribution.
385+
386+ Create IDistributionSourcePackages by invoking
387+ `IDistribution.getSourcePackage()`.
388+ """
389diff --git a/lib/lp/registry/interfaces/product.py b/lib/lp/registry/interfaces/product.py
390index e28f68c..a3e3e45 100644
391--- a/lib/lp/registry/interfaces/product.py
392+++ b/lib/lp/registry/interfaces/product.py
393@@ -130,6 +130,7 @@ from lp.services.fields import (
394 Title,
395 URIField,
396 )
397+from lp.services.webhooks.interfaces import IWebhookTarget
398 from lp.services.webservice.apihelpers import (
399 patch_collection_property,
400 patch_reference_property,
401@@ -1099,7 +1100,7 @@ class IProductView(
402 """
403
404
405-class IProductEditRestricted(IOfficialBugTagTargetRestricted):
406+class IProductEditRestricted(IOfficialBugTagTargetRestricted, IWebhookTarget):
407 """`IProduct` properties which require launchpad.Edit permission."""
408
409 @mutator_for(IProductView["bug_sharing_policy"])
410diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
411index cf812a7..045fb98 100644
412--- a/lib/lp/registry/model/distribution.py
413+++ b/lib/lp/registry/model/distribution.py
414@@ -172,6 +172,7 @@ from lp.services.helpers import backslashreplace, shortlist
415 from lp.services.propertycache import cachedproperty, get_property_cache
416 from lp.services.webapp.interfaces import ILaunchBag
417 from lp.services.webapp.url import urlparse
418+from lp.services.webhooks.model import WebhookTargetMixin
419 from lp.services.worlddata.model.country import Country
420 from lp.soyuz.enums import (
421 ArchivePurpose,
422@@ -238,6 +239,7 @@ class Distribution(
423 TranslationPolicyMixin,
424 InformationTypeMixin,
425 SharingPolicyMixin,
426+ WebhookTargetMixin,
427 ):
428 """A distribution of an operating system, e.g. Debian GNU/Linux."""
429
430@@ -2312,6 +2314,10 @@ class Distribution(
431 .one()
432 )
433
434+ @property
435+ def valid_webhook_event_types(self):
436+ return ["bug:0.1", "bug:comment:0.1"]
437+
438
439 @implementer(IDistributionSet)
440 class DistributionSet:
441diff --git a/lib/lp/registry/model/distributionsourcepackage.py b/lib/lp/registry/model/distributionsourcepackage.py
442index 0b6a1ce..5235ff7 100644
443--- a/lib/lp/registry/model/distributionsourcepackage.py
444+++ b/lib/lp/registry/model/distributionsourcepackage.py
445@@ -46,6 +46,7 @@ from lp.services.database.decoratedresultset import DecoratedResultSet
446 from lp.services.database.interfaces import IStore
447 from lp.services.database.stormbase import StormBase
448 from lp.services.propertycache import cachedproperty
449+from lp.services.webhooks.model import WebhookTargetMixin
450 from lp.soyuz.enums import ArchivePurpose, PackagePublishingStatus
451 from lp.soyuz.model.archive import Archive
452 from lp.soyuz.model.distributionsourcepackagerelease import (
453@@ -88,6 +89,7 @@ class DistributionSourcePackage(
454 HasCustomLanguageCodesMixin,
455 HasMergeProposalsMixin,
456 HasDriversMixin,
457+ WebhookTargetMixin,
458 ):
459 """This is a "Magic Distribution Source Package". It is not an
460 SQLObject, but instead it represents a source package with a particular
461@@ -561,6 +563,10 @@ class DistributionSourcePackage(
462 if dsp is None:
463 cls._new(distribution, sourcepackagename)
464
465+ @property
466+ def valid_webhook_event_types(self):
467+ return ["bug:0.1", "bug:comment:0.1"]
468+
469
470 @implementer(transaction.interfaces.ISynchronizer)
471 class ThreadLocalLRUCache(LRUCache, local):
472diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
473index 4bbf796..3c95e99 100644
474--- a/lib/lp/registry/model/product.py
475+++ b/lib/lp/registry/model/product.py
476@@ -164,6 +164,7 @@ from lp.services.propertycache import cachedproperty, get_property_cache
477 from lp.services.statistics.interfaces.statistic import ILaunchpadStatisticSet
478 from lp.services.webapp.interfaces import ILaunchBag
479 from lp.services.webapp.snapshot import notify_modified
480+from lp.services.webhooks.model import WebhookTargetMixin
481 from lp.translations.enums import TranslationPermission
482 from lp.translations.interfaces.customlanguagecode import (
483 IHasCustomLanguageCodes,
484@@ -265,6 +266,7 @@ class Product(
485 HasAliasMixin,
486 HasCustomLanguageCodesMixin,
487 SharingPolicyMixin,
488+ WebhookTargetMixin,
489 ):
490 """A Product."""
491
492@@ -1593,6 +1595,10 @@ class Product(
493 .is_empty()
494 )
495
496+ @property
497+ def valid_webhook_event_types(self):
498+ return ["bug:0.1", "bug:comment:0.1"]
499+
500
501 def get_precached_products(
502 products,
503diff --git a/lib/lp/services/features/flags.py b/lib/lp/services/features/flags.py
504index 4b19185..5d42d9d 100644
505--- a/lib/lp/services/features/flags.py
506+++ b/lib/lp/services/features/flags.py
507@@ -295,6 +295,14 @@ flag_info = sorted(
508 "",
509 "",
510 ),
511+ (
512+ "bugs.webhooks.enabled",
513+ "boolean",
514+ "If true, allow adding webhooks to bug updates and comments",
515+ "",
516+ "",
517+ "",
518+ ),
519 ]
520 )
521
522diff --git a/lib/lp/services/webhooks/interfaces.py b/lib/lp/services/webhooks/interfaces.py
523index 4f0a1e8..306cffd 100644
524--- a/lib/lp/services/webhooks/interfaces.py
525+++ b/lib/lp/services/webhooks/interfaces.py
526@@ -48,6 +48,8 @@ from lp.services.webservice.apihelpers import (
527 )
528
529 WEBHOOK_EVENT_TYPES = {
530+ "bug:0.1": "Bug creation/change",
531+ "bug:comment:0.1": "Bug comment",
532 "bzr:push:0.1": "Bazaar push",
533 "charm-recipe:build:0.1": "Charm recipe build",
534 "ci:build:0.1": "CI build",
535diff --git a/lib/lp/services/webhooks/tests/test_browser.py b/lib/lp/services/webhooks/tests/test_browser.py
536index b3f28fe..7542cae 100644
537--- a/lib/lp/services/webhooks/tests/test_browser.py
538+++ b/lib/lp/services/webhooks/tests/test_browser.py
539@@ -10,6 +10,7 @@ import transaction
540 from testtools.matchers import MatchesAll, MatchesStructure, Not
541 from zope.component import getUtility
542
543+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
544 from lp.charms.interfaces.charmrecipe import (
545 CHARM_RECIPE_ALLOW_CREATE,
546 CHARM_RECIPE_WEBHOOKS_FEATURE_FLAG,
547@@ -174,13 +175,54 @@ class CharmRecipeTestHelpers:
548 return [obj]
549
550
551+class BugUpdateTestHelpersBase:
552+
553+ # Overriding this since product webhooks don't have breadcrumbs
554+ _webhook_listing = soupmatchers.HTMLContains(add_webhook_tag)
555+
556+ event_type = "bug:0.1"
557+ expected_event_types = [
558+ ("bug:0.1", "Bug change"),
559+ ("bug:comment:0.1", "Bug comment"),
560+ ]
561+
562+ def getTraversalStack(self, obj):
563+ return [obj]
564+
565+
566+class ProductTestHelpers(BugUpdateTestHelpersBase):
567+ def makeTarget(self):
568+ self.useFixture(FeatureFixture({BUG_WEBHOOKS_FEATURE_FLAG: "on"}))
569+ owner = self.factory.makePerson()
570+ return self.factory.makeProduct(owner=owner)
571+
572+
573+class DistributionTestHelpers(BugUpdateTestHelpersBase):
574+ def makeTarget(self):
575+ self.useFixture(FeatureFixture({BUG_WEBHOOKS_FEATURE_FLAG: "on"}))
576+ owner = self.factory.makePerson()
577+ return self.factory.makeDistribution(owner=owner)
578+
579+
580+class DistributionSourcePackageTestHelpers(BugUpdateTestHelpersBase):
581+ def get_target_owner(self):
582+ return self.target.distribution.owner
583+
584+ def makeTarget(self):
585+ self.useFixture(FeatureFixture({BUG_WEBHOOKS_FEATURE_FLAG: "on"}))
586+ return self.factory.makeDistributionSourcePackage()
587+
588+
589 class WebhookTargetViewTestHelpers:
590 def setUp(self):
591 super().setUp()
592 self.target = self.makeTarget()
593- self.owner = self.target.owner
594+ self.owner = self.get_target_owner()
595 login_person(self.owner)
596
597+ def get_target_owner(self):
598+ return self.target.owner
599+
600 def makeView(self, name, **kwargs):
601 # XXX cjwatson 2020-02-06: We need to give the view a
602 # LaunchpadPrincipal rather than just a person, since otherwise bits
603@@ -212,6 +254,7 @@ class WebhookTargetViewTestHelpers:
604 class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
605
606 layer = DatabaseFunctionalLayer
607+ _webhook_listing = webhook_listing_constants
608
609 def makeHooksAndMatchers(self, count):
610 hooks = [
611@@ -257,7 +300,7 @@ class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
612 self.assertThat(
613 self.makeView("+webhooks")(),
614 MatchesAll(
615- webhook_listing_constants,
616+ self._webhook_listing,
617 Not(soupmatchers.HTMLContains(webhook_listing_tag)),
618 ),
619 )
620@@ -268,7 +311,7 @@ class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
621 self.assertThat(
622 self.makeView("+webhooks")(),
623 MatchesAll(
624- webhook_listing_constants,
625+ self._webhook_listing,
626 soupmatchers.HTMLContains(webhook_listing_tag, *link_matchers),
627 Not(soupmatchers.HTMLContains(batch_nav_tag)),
628 ),
629@@ -280,7 +323,7 @@ class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
630 self.assertThat(
631 self.makeView("+webhooks")(),
632 MatchesAll(
633- webhook_listing_constants,
634+ self._webhook_listing,
635 soupmatchers.HTMLContains(
636 webhook_listing_tag, batch_nav_tag, *link_matchers[:5]
637 ),
638@@ -343,6 +386,29 @@ class TestWebhooksViewCharmRecipe(
639 pass
640
641
642+class TestWebhooksViewProductBugUpdate(
643+ ProductTestHelpers, TestWebhooksViewBase, TestCaseWithFactory
644+):
645+
646+ pass
647+
648+
649+class TestWebhooksViewDistributionBugUpdate(
650+ DistributionTestHelpers, TestWebhooksViewBase, TestCaseWithFactory
651+):
652+
653+ pass
654+
655+
656+class TestWebhooksViewDistributionSourcePackageBugUpdate(
657+ DistributionSourcePackageTestHelpers,
658+ TestWebhooksViewBase,
659+ TestCaseWithFactory,
660+):
661+
662+ pass
663+
664+
665 class TestWebhookAddViewBase(WebhookTargetViewTestHelpers):
666
667 layer = DatabaseFunctionalLayer
668@@ -492,16 +558,42 @@ class TestWebhookAddViewCharmRecipe(
669 pass
670
671
672+class TestWebhookAddViewProductBugUpdate(
673+ ProductTestHelpers, TestWebhookAddViewBase, TestCaseWithFactory
674+):
675+
676+ pass
677+
678+
679+class TestWebhookAddViewDistributionBugUpdate(
680+ DistributionTestHelpers, TestWebhookAddViewBase, TestCaseWithFactory
681+):
682+
683+ pass
684+
685+
686+class TestWebhookAddViewDistributionSourcePackageBugUpdate(
687+ DistributionSourcePackageTestHelpers,
688+ TestWebhookAddViewBase,
689+ TestCaseWithFactory,
690+):
691+
692+ pass
693+
694+
695 class WebhookViewTestHelpers:
696 def setUp(self):
697 super().setUp()
698 self.target = self.makeTarget()
699- self.owner = self.target.owner
700+ self.owner = self.get_target_owner()
701 self.webhook = self.factory.makeWebhook(
702 target=self.target, delivery_url="http://example.com/original"
703 )
704 login_person(self.owner)
705
706+ def get_target_owner(self):
707+ return self.target.owner
708+
709 def makeView(self, name, **kwargs):
710 view = create_view(self.webhook, name, principal=self.owner, **kwargs)
711 # To test the breadcrumbs we need a correct traversal stack.
712@@ -729,3 +821,26 @@ class TestWebhookDeleteViewCharmRecipe(
713 ):
714
715 pass
716+
717+
718+class TestWebhookDeleteViewProductBugUpdate(
719+ ProductTestHelpers, TestWebhookDeleteViewBase, TestCaseWithFactory
720+):
721+
722+ pass
723+
724+
725+class TestWebhookDeleteViewDistributionBugUpdate(
726+ DistributionTestHelpers, TestWebhookDeleteViewBase, TestCaseWithFactory
727+):
728+
729+ pass
730+
731+
732+class TestWebhookDeleteViewDistributionSourcePackageBugUpdate(
733+ DistributionSourcePackageTestHelpers,
734+ TestWebhookDeleteViewBase,
735+ TestCaseWithFactory,
736+):
737+
738+ pass
739diff --git a/lib/lp/services/webhooks/tests/test_webservice.py b/lib/lp/services/webhooks/tests/test_webservice.py
740index 7f84a14..a6f9c0b 100644
741--- a/lib/lp/services/webhooks/tests/test_webservice.py
742+++ b/lib/lp/services/webhooks/tests/test_webservice.py
743@@ -45,17 +45,20 @@ class TestWebhook(TestCaseWithFactory):
744
745 def setUp(self):
746 super().setUp()
747- target = self.factory.makeGitRepository()
748- self.owner = target.owner
749+ self.target = self.factory.makeGitRepository()
750+ self.owner = self.get_target_owner()
751 with person_logged_in(self.owner):
752 self.webhook = self.factory.makeWebhook(
753- target=target, delivery_url="http://example.com/ep"
754+ target=self.target, delivery_url="http://example.com/ep"
755 )
756 self.webhook_url = api_url(self.webhook)
757 self.webservice = webservice_for_person(
758 self.owner, permission=OAuthPermission.WRITE_PRIVATE
759 )
760
761+ def get_target_owner(self):
762+ return self.target.owner
763+
764 def test_get(self):
765 representation = self.webservice.get(
766 self.webhook_url, api_version="devel"
767@@ -262,11 +265,11 @@ class TestWebhookDelivery(TestCaseWithFactory):
768
769 def setUp(self):
770 super().setUp()
771- target = self.factory.makeGitRepository()
772- self.owner = target.owner
773+ self.target = self.factory.makeGitRepository()
774+ self.owner = self.get_target_owner()
775 with person_logged_in(self.owner):
776 self.webhook = self.factory.makeWebhook(
777- target=target, delivery_url="http://example.com/ep"
778+ target=self.target, delivery_url="http://example.com/ep"
779 )
780 self.webhook_url = api_url(self.webhook)
781 self.delivery = self.webhook.ping()
782@@ -275,6 +278,9 @@ class TestWebhookDelivery(TestCaseWithFactory):
783 self.owner, permission=OAuthPermission.WRITE_PRIVATE
784 )
785
786+ def get_target_owner(self):
787+ return self.target.owner
788+
789 def test_get(self):
790 representation = self.webservice.get(
791 self.delivery_url, api_version="devel"
792@@ -355,12 +361,15 @@ class TestWebhookTargetBase:
793 def setUp(self):
794 super().setUp()
795 self.target = self.makeTarget()
796- self.owner = self.target.owner
797+ self.owner = self.get_target_owner()
798 self.target_url = api_url(self.target)
799 self.webservice = webservice_for_person(
800 self.owner, permission=OAuthPermission.WRITE_PRIVATE
801 )
802
803+ def get_target_owner(self):
804+ return self.target.owner
805+
806 def test_webhooks(self):
807 with person_logged_in(self.owner):
808 for ep in ("http://example.com/ep1", "http://example.com/ep2"):
809@@ -511,3 +520,36 @@ class TestWebhookTargetCharmRecipe(TestWebhookTargetBase, TestCaseWithFactory):
810 }
811 ):
812 return self.factory.makeCharmRecipe(registrant=owner, owner=owner)
813+
814+
815+class TestWebhookTargetProduct(TestWebhookTargetBase, TestCaseWithFactory):
816+
817+ event_type = "bug:0.1"
818+
819+ def makeTarget(self):
820+ owner = self.factory.makePerson()
821+ return self.factory.makeProduct(owner=owner)
822+
823+
824+class TestWebhookTargetDistribution(
825+ TestWebhookTargetBase, TestCaseWithFactory
826+):
827+
828+ event_type = "bug:0.1"
829+
830+ def makeTarget(self):
831+ owner = self.factory.makePerson()
832+ return self.factory.makeDistribution(owner=owner)
833+
834+
835+class TestWebhookTargetDistributionSourcePackage(
836+ TestWebhookTargetBase, TestCaseWithFactory
837+):
838+
839+ event_type = "bug:0.1"
840+
841+ def makeTarget(self):
842+ return self.factory.makeDistributionSourcePackage()
843+
844+ def get_target_owner(self):
845+ return self.target.distribution.owner

Subscribers

People subscribed via source and target branches

to status/vote changes: