launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #29984
[Merge] ~ines-almeida/launchpad:add-bug-webhooks/add-interfaces into launchpad:master
Ines Almeida has proposed merging ~ines-almeida/launchpad:add-bug-webhooks/add-interfaces into launchpad:master with ~ines-almeida/launchpad:add-bug-webhooks/update-webhook-model as a prerequisite.
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.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/442544
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
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ines-almeida/launchpad:add-bug-webhooks/add-interfaces into launchpad:master.
diff --git a/database/schema/patch-2211-19-0.sql b/database/schema/patch-2211-19-0.sql
new file mode 100644
index 0000000..d0f3a80
--- /dev/null
+++ b/database/schema/patch-2211-19-0.sql
@@ -0,0 +1,14 @@
+-- Copyright 2011 Canonical Ltd. This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+ALTER TABLE Webhook
+ ADD COLUMN project integer REFERENCES product,
+ ADD COLUMN distribution integer REFERENCES distribution,
+ ADD COLUMN source_package_name integer REFERENCES sourcepackagename;
+
+ALTER TABLE Webhook DROP CONSTRAINT one_target;
+ALTER TABLE Webhook ADD CONSTRAINT one_target CHECK ((public.null_count(ARRAY[git_repository, branch, snap, livefs, oci_recipe, charm_recipe, project, distribution]) = 7));
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2211, 19, 0);
\ No newline at end of file
diff --git a/lib/lp/bugs/interfaces/bugtarget.py b/lib/lp/bugs/interfaces/bugtarget.py
index 57bc0fe..540079f 100644
--- a/lib/lp/bugs/interfaces/bugtarget.py
+++ b/lib/lp/bugs/interfaces/bugtarget.py
@@ -15,6 +15,7 @@ __all__ = [
"ISeriesBugTarget",
"BUG_POLICY_ALLOWED_TYPES",
"BUG_POLICY_DEFAULT_TYPES",
+ "BUG_WEBHOOKS_FEATURE_FLAG",
]
@@ -156,6 +157,9 @@ BUG_POLICY_DEFAULT_TYPES = {
}
+BUG_WEBHOOKS_FEATURE_FLAG = "bugs.webhooks.enabled"
+
+
@exported_as_webservice_entry(as_of="beta")
class IHasBugs(Interface):
"""An entity which has a collection of bug tasks."""
diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
index 3fb8a36..b990f6f 100644
--- a/lib/lp/registry/browser/distribution.py
+++ b/lib/lp/registry/browser/distribution.py
@@ -83,6 +83,7 @@ from lp.bugs.browser.structuralsubscription import (
StructuralSubscriptionTargetTraversalMixin,
expose_structural_subscription_data_to_js,
)
+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
from lp.buildmaster.interfaces.processor import IProcessorSet
from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
from lp.registry.browser import RegistryEditFormView, add_subscribe_link
@@ -141,6 +142,7 @@ from lp.services.webapp.authorization import check_permission
from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.interfaces import ILaunchBag
+from lp.services.webhooks.browser import WebhookTargetNavigationMixin
from lp.soyuz.browser.archive import EnableProcessorsMixin
from lp.soyuz.browser.packagesearch import PackageSearchViewBase
from lp.soyuz.enums import ArchivePurpose
@@ -155,6 +157,7 @@ class DistributionNavigation(
StructuralSubscriptionTargetTraversalMixin,
PillarNavigationMixin,
TargetDefaultVCSNavigationMixin,
+ WebhookTargetNavigationMixin,
):
usedfor = IDistribution
@@ -388,6 +391,18 @@ class DistributionNavigationMenu(NavigationMenu, DistributionLinksMixin):
usedfor = IDistribution
facet = "overview"
+ links = (
+ "edit",
+ "admin",
+ "pubconf",
+ "subscribe_to_bug_mail",
+ "edit_bug_mail",
+ "sharing",
+ "new_oci_project",
+ "search_oci_project",
+ "webhooks",
+ )
+
@enabled_with_permission("launchpad.Admin")
def admin(self):
text = "Administer"
@@ -419,18 +434,14 @@ class DistributionNavigationMenu(NavigationMenu, DistributionLinksMixin):
link.enabled = not oci_projects.is_empty()
return link
- @cachedproperty
- def links(self):
- return [
- "edit",
- "admin",
- "pubconf",
- "subscribe_to_bug_mail",
- "edit_bug_mail",
- "sharing",
- "new_oci_project",
- "search_oci_project",
- ]
+ @enabled_with_permission("launchpad.Edit")
+ def webhooks(self):
+ return Link(
+ "+webhooks",
+ "Manage webhooks",
+ icon="edit",
+ enabled=bool(getFeatureFlag(BUG_WEBHOOKS_FEATURE_FLAG)),
+ )
class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin):
@@ -623,6 +634,7 @@ class DistributionBugsMenu(PillarBugsMenu):
def links(self):
links = ["bugsupervisor", "cve", "filebug"]
add_subscribe_link(links)
+ links.append("webhooks")
return links
diff --git a/lib/lp/registry/browser/distributionsourcepackage.py b/lib/lp/registry/browser/distributionsourcepackage.py
index a55097b..e0d960a 100644
--- a/lib/lp/registry/browser/distributionsourcepackage.py
+++ b/lib/lp/registry/browser/distributionsourcepackage.py
@@ -42,6 +42,7 @@ from lp.bugs.browser.structuralsubscription import (
StructuralSubscriptionTargetTraversalMixin,
expose_structural_subscription_data_to_js,
)
+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
from lp.bugs.interfaces.bugtask import BugTaskStatus
from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
@@ -54,6 +55,7 @@ from lp.registry.interfaces.distributionsourcepackage import (
from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.series import SeriesStatus
from lp.services.database.decoratedresultset import DecoratedResultSet
+from lp.services.features import getFeatureFlag
from lp.services.helpers import shortlist
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
@@ -76,6 +78,7 @@ from lp.services.webapp.menu import (
)
from lp.services.webapp.publisher import LaunchpadView
from lp.services.webapp.sorting import sorted_dotted_numbers
+from lp.services.webhooks.browser import WebhookTargetNavigationMixin
from lp.soyuz.browser.sourcepackagerelease import linkify_changelog
from lp.soyuz.interfaces.archive import IArchiveSet
from lp.soyuz.interfaces.distributionsourcepackagerelease import (
@@ -170,6 +173,15 @@ class DistributionSourcePackageLinksMixin:
get_data = "?field.status=OPEN"
return Link(base_path + get_data, "Open Questions", site="answers")
+ @enabled_with_permission("launchpad.Edit")
+ def webhooks(self):
+ return Link(
+ "+webhooks",
+ "Manage webhooks",
+ icon="edit",
+ enabled=bool(getFeatureFlag(BUG_WEBHOOKS_FEATURE_FLAG)),
+ )
+
class DistributionSourcePackageOverviewMenu(
ApplicationMenu, DistributionSourcePackageLinksMixin
@@ -193,6 +205,7 @@ class DistributionSourcePackageBugsMenu(
def links(self):
links = ["filebug"]
add_subscribe_link(links)
+ links.append("webhooks")
return links
@@ -214,6 +227,7 @@ class DistributionSourcePackageNavigation(
QuestionTargetTraversalMixin,
TargetDefaultVCSNavigationMixin,
StructuralSubscriptionTargetTraversalMixin,
+ WebhookTargetNavigationMixin,
):
usedfor = IDistributionSourcePackage
@@ -285,7 +299,7 @@ class DistributionSourcePackageActionMenu(
def links(self):
links = ["publishing_history", "change_log"]
add_subscribe_link(links)
- links.append("edit")
+ links.extend(["edit", "webhooks"])
return links
def publishing_history(self):
diff --git a/lib/lp/registry/browser/product.py b/lib/lp/registry/browser/product.py
index 56c03a3..907d7f1 100644
--- a/lib/lp/registry/browser/product.py
+++ b/lib/lp/registry/browser/product.py
@@ -111,6 +111,7 @@ from lp.bugs.browser.structuralsubscription import (
StructuralSubscriptionTargetTraversalMixin,
expose_structural_subscription_data_to_js,
)
+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES
from lp.charms.browser.hascharmrecipes import HasCharmRecipesMenuMixin
from lp.code.browser.branchref import BranchRef
@@ -190,6 +191,7 @@ from lp.services.webapp.interfaces import UnsafeFormGetSubmissionError
from lp.services.webapp.menu import NavigationMenu
from lp.services.webapp.url import urlsplit
from lp.services.webapp.vhosts import allvhosts
+from lp.services.webhooks.browser import WebhookTargetNavigationMixin
from lp.services.worlddata.helpers import browser_languages
from lp.services.worlddata.interfaces.country import ICountry
from lp.snappy.browser.hassnaps import HasSnapsMenuMixin
@@ -210,6 +212,7 @@ class ProductNavigation(
StructuralSubscriptionTargetTraversalMixin,
PillarNavigationMixin,
TargetDefaultVCSNavigationMixin,
+ WebhookTargetNavigationMixin,
):
usedfor = IProduct
@@ -512,6 +515,15 @@ class ProductEditLinksMixin(StructuralSubscriptionMenuMixin):
) and product.canAdministerOCIProjects(self.user)
return link
+ @enabled_with_permission("launchpad.Edit")
+ def webhooks(self):
+ return Link(
+ "+webhooks",
+ "Manage webhooks",
+ icon="edit",
+ enabled=bool(getFeatureFlag(BUG_WEBHOOKS_FEATURE_FLAG)),
+ )
+
class IProductEditMenu(Interface):
"""A marker interface for the 'Change details' navigation menu."""
@@ -537,6 +549,7 @@ class ProductActionNavigationMenu(NavigationMenu, ProductEditLinksMixin):
"sharing",
"search_oci_project",
"new_oci_project",
+ "webhooks",
]
add_subscribe_link(links)
return links
@@ -648,7 +661,7 @@ class ProductBugsMenu(PillarBugsMenu, ProductEditLinksMixin):
def links(self):
links = ["filebug", "bugsupervisor", "cve"]
add_subscribe_link(links)
- links.append("configure_bugtracker")
+ links.extend(["configure_bugtracker", "webhooks"])
return links
diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml
index 22178f0..b2dc87f 100644
--- a/lib/lp/registry/configure.zcml
+++ b/lib/lp/registry/configure.zcml
@@ -550,48 +550,14 @@
<class
class="lp.registry.model.distributionsourcepackage.DistributionSourcePackage">
<allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
- <allow interface="lp.bugs.interfaces.bugtarget.IBugTarget"/>
<allow
interface="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"/>
<allow
- attributes="
- __eq__
- __getitem__
- __ne__
- _getOfficialTagClause
- binary_names
- bug_count
- bugtasks
- current_publishing_records
- currentrelease
- delete
- development_version
- display_name
- displayname
- distribution
- distro
- drivers
- findRelatedArchivePublications
- findRelatedArchives
- getBranches
- getMergeProposals
- getReleasesAndPublishingHistory
- getUsedBugTagsWithOpenCounts
- getVersion
- get_distroseries_packages
- is_official
- latest_overall_publication
- name
- official_bug_tags
- personHasDriverRights
- publishing_history
- releases
- sourcepackagename
- subscribers
- summary
- title
- upstream_product"/>
+ interface="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackageView"/>
+ <require
+ permission="launchpad.Edit"
+ interface="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackageEdit"/>
<!-- IStructuralSubscriptionTarget -->
@@ -615,16 +581,6 @@
bug_reporting_guidelines
enable_bugfiling_duplicate_search
"/>
-
- <!-- IHasGitRepositories -->
-
- <allow
- interface="lp.code.interfaces.hasgitrepositories.IHasGitRepositories" />
-
- <!-- IHasCodeImports -->
-
- <allow
- interface="lp.code.interfaces.hasbranches.IHasCodeImports" />
</class>
<adapter
provides="lp.registry.interfaces.distribution.IDistribution"
diff --git a/lib/lp/registry/interfaces/distribution.py b/lib/lp/registry/interfaces/distribution.py
index 7fdc5ff..ef966c7 100644
--- a/lib/lp/registry/interfaces/distribution.py
+++ b/lib/lp/registry/interfaces/distribution.py
@@ -102,6 +102,7 @@ from lp.services.fields import (
Summary,
Title,
)
+from lp.services.webhooks.interfaces import IWebhookTarget
from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
from lp.translations.interfaces.hastranslationimports import (
IHasTranslationImports,
@@ -1064,7 +1065,9 @@ class IDistributionView(
"""Return the vulnerability in this distribution with the given id."""
-class IDistributionEditRestricted(IOfficialBugTagTargetRestricted):
+class IDistributionEditRestricted(
+ IOfficialBugTagTargetRestricted, IWebhookTarget
+):
"""IDistribution properties requiring launchpad.Edit permission."""
@mutator_for(IDistributionView["bug_sharing_policy"])
diff --git a/lib/lp/registry/interfaces/distributionsourcepackage.py b/lib/lp/registry/interfaces/distributionsourcepackage.py
index cba5873..11235d9 100644
--- a/lib/lp/registry/interfaces/distributionsourcepackage.py
+++ b/lib/lp/registry/interfaces/distributionsourcepackage.py
@@ -27,27 +27,22 @@ from lp.code.interfaces.hasbranches import (
from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.role import IHasDrivers
+from lp.services.webhooks.interfaces import IWebhookTarget
from lp.soyuz.enums import ArchivePurpose
@exported_as_webservice_entry(as_of="beta")
-class IDistributionSourcePackage(
+class IDistributionSourcePackageView(
IHeadingContext,
IBugTarget,
IHasBranches,
IHasMergeProposals,
IHasOfficialBugTags,
- IStructuralSubscriptionTarget,
- IQuestionTarget,
IHasDrivers,
IHasGitRepositories,
IHasCodeImports,
):
- """Represents a source package in a distribution.
-
- Create IDistributionSourcePackages by invoking
- `IDistribution.getSourcePackage()`.
- """
+ """`IDistributionSourcePackage` attributes that require launchpad.View."""
distribution = exported(
Reference(IDistribution, title=_("The distribution."))
@@ -112,6 +107,7 @@ class IDistributionSourcePackage(
"of the IDistributionSourcePackage."
)
+ # XXX unused
po_message_count = Attribute(
"Number of translations matching the distribution and "
"sourcepackagename of the IDistributionSourcePackage."
@@ -209,3 +205,21 @@ class IDistributionSourcePackage(
:return: True if a persistent object was removed, otherwise False.
"""
+
+
+class IDistributionSourcePackageEdit(IWebhookTarget):
+ """`IDistributionSourcePackage` attributes that require launchpad.Edit."""
+
+
+@exported_as_webservice_entry(as_of="beta")
+class IDistributionSourcePackage(
+ IDistributionSourcePackageView,
+ IDistributionSourcePackageEdit,
+ IStructuralSubscriptionTarget,
+ IQuestionTarget,
+):
+ """Represents a source package in a distribution.
+
+ Create IDistributionSourcePackages by invoking
+ `IDistribution.getSourcePackage()`.
+ """
diff --git a/lib/lp/registry/interfaces/product.py b/lib/lp/registry/interfaces/product.py
index e28f68c..a3e3e45 100644
--- a/lib/lp/registry/interfaces/product.py
+++ b/lib/lp/registry/interfaces/product.py
@@ -130,6 +130,7 @@ from lp.services.fields import (
Title,
URIField,
)
+from lp.services.webhooks.interfaces import IWebhookTarget
from lp.services.webservice.apihelpers import (
patch_collection_property,
patch_reference_property,
@@ -1099,7 +1100,7 @@ class IProductView(
"""
-class IProductEditRestricted(IOfficialBugTagTargetRestricted):
+class IProductEditRestricted(IOfficialBugTagTargetRestricted, IWebhookTarget):
"""`IProduct` properties which require launchpad.Edit permission."""
@mutator_for(IProductView["bug_sharing_policy"])
diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
index cf812a7..045fb98 100644
--- a/lib/lp/registry/model/distribution.py
+++ b/lib/lp/registry/model/distribution.py
@@ -172,6 +172,7 @@ from lp.services.helpers import backslashreplace, shortlist
from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.url import urlparse
+from lp.services.webhooks.model import WebhookTargetMixin
from lp.services.worlddata.model.country import Country
from lp.soyuz.enums import (
ArchivePurpose,
@@ -238,6 +239,7 @@ class Distribution(
TranslationPolicyMixin,
InformationTypeMixin,
SharingPolicyMixin,
+ WebhookTargetMixin,
):
"""A distribution of an operating system, e.g. Debian GNU/Linux."""
@@ -2312,6 +2314,10 @@ class Distribution(
.one()
)
+ @property
+ def valid_webhook_event_types(self):
+ return ["bug:0.1", "bug:comment:0.1"]
+
@implementer(IDistributionSet)
class DistributionSet:
diff --git a/lib/lp/registry/model/distributionsourcepackage.py b/lib/lp/registry/model/distributionsourcepackage.py
index 0b6a1ce..5235ff7 100644
--- a/lib/lp/registry/model/distributionsourcepackage.py
+++ b/lib/lp/registry/model/distributionsourcepackage.py
@@ -46,6 +46,7 @@ from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.interfaces import IStore
from lp.services.database.stormbase import StormBase
from lp.services.propertycache import cachedproperty
+from lp.services.webhooks.model import WebhookTargetMixin
from lp.soyuz.enums import ArchivePurpose, PackagePublishingStatus
from lp.soyuz.model.archive import Archive
from lp.soyuz.model.distributionsourcepackagerelease import (
@@ -88,6 +89,7 @@ class DistributionSourcePackage(
HasCustomLanguageCodesMixin,
HasMergeProposalsMixin,
HasDriversMixin,
+ WebhookTargetMixin,
):
"""This is a "Magic Distribution Source Package". It is not an
SQLObject, but instead it represents a source package with a particular
@@ -561,6 +563,10 @@ class DistributionSourcePackage(
if dsp is None:
cls._new(distribution, sourcepackagename)
+ @property
+ def valid_webhook_event_types(self):
+ return ["bug:0.1", "bug:comment:0.1"]
+
@implementer(transaction.interfaces.ISynchronizer)
class ThreadLocalLRUCache(LRUCache, local):
diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
index 4bbf796..3c95e99 100644
--- a/lib/lp/registry/model/product.py
+++ b/lib/lp/registry/model/product.py
@@ -164,6 +164,7 @@ from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.statistics.interfaces.statistic import ILaunchpadStatisticSet
from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.snapshot import notify_modified
+from lp.services.webhooks.model import WebhookTargetMixin
from lp.translations.enums import TranslationPermission
from lp.translations.interfaces.customlanguagecode import (
IHasCustomLanguageCodes,
@@ -265,6 +266,7 @@ class Product(
HasAliasMixin,
HasCustomLanguageCodesMixin,
SharingPolicyMixin,
+ WebhookTargetMixin,
):
"""A Product."""
@@ -1593,6 +1595,10 @@ class Product(
.is_empty()
)
+ @property
+ def valid_webhook_event_types(self):
+ return ["bug:0.1", "bug:comment:0.1"]
+
def get_precached_products(
products,
diff --git a/lib/lp/services/features/flags.py b/lib/lp/services/features/flags.py
index 4b19185..5d42d9d 100644
--- a/lib/lp/services/features/flags.py
+++ b/lib/lp/services/features/flags.py
@@ -295,6 +295,14 @@ flag_info = sorted(
"",
"",
),
+ (
+ "bugs.webhooks.enabled",
+ "boolean",
+ "If true, allow adding webhooks to bug updates and comments",
+ "",
+ "",
+ "",
+ ),
]
)
diff --git a/lib/lp/services/webhooks/interfaces.py b/lib/lp/services/webhooks/interfaces.py
index b31f29d..898aeb8 100644
--- a/lib/lp/services/webhooks/interfaces.py
+++ b/lib/lp/services/webhooks/interfaces.py
@@ -48,6 +48,8 @@ from lp.services.webservice.apihelpers import (
)
WEBHOOK_EVENT_TYPES = {
+ "bug:0.1": "Bug change",
+ "bug:comment:0.1": "Bug comment",
"bzr:push:0.1": "Bazaar push",
"charm-recipe:build:0.1": "Charm recipe build",
"git:push:0.1": "Git push",
diff --git a/lib/lp/services/webhooks/tests/test_browser.py b/lib/lp/services/webhooks/tests/test_browser.py
index 65ba0c3..a155c00 100644
--- a/lib/lp/services/webhooks/tests/test_browser.py
+++ b/lib/lp/services/webhooks/tests/test_browser.py
@@ -10,6 +10,7 @@ import transaction
from testtools.matchers import MatchesAll, MatchesStructure, Not
from zope.component import getUtility
+from lp.bugs.interfaces.bugtarget import BUG_WEBHOOKS_FEATURE_FLAG
from lp.charms.interfaces.charmrecipe import (
CHARM_RECIPE_ALLOW_CREATE,
CHARM_RECIPE_WEBHOOKS_FEATURE_FLAG,
@@ -173,13 +174,54 @@ class CharmRecipeTestHelpers:
return [obj]
+class BugUpdateTestHelpersBase:
+
+ # Overriding this since product webhooks don't have breadcrumbs
+ _webhook_listing = soupmatchers.HTMLContains(add_webhook_tag)
+
+ event_type = "bug:0.1"
+ expected_event_types = [
+ ("bug:0.1", "Bug change"),
+ ("bug:comment:0.1", "Bug comment"),
+ ]
+
+ def getTraversalStack(self, obj):
+ return [obj]
+
+
+class ProductTestHelpers(BugUpdateTestHelpersBase):
+ def makeTarget(self):
+ self.useFixture(FeatureFixture({BUG_WEBHOOKS_FEATURE_FLAG: "on"}))
+ owner = self.factory.makePerson()
+ return self.factory.makeProduct(owner=owner)
+
+
+class DistributionTestHelpers(BugUpdateTestHelpersBase):
+ def makeTarget(self):
+ self.useFixture(FeatureFixture({BUG_WEBHOOKS_FEATURE_FLAG: "on"}))
+ owner = self.factory.makePerson()
+ return self.factory.makeDistribution(owner=owner)
+
+
+class DistributionSourcePackageTestHelpers(BugUpdateTestHelpersBase):
+ def get_target_owner(self):
+ return self.target.distribution.owner
+
+ def makeTarget(self):
+ self.useFixture(FeatureFixture({BUG_WEBHOOKS_FEATURE_FLAG: "on"}))
+ return self.factory.makeDistributionSourcePackage()
+
+
class WebhookTargetViewTestHelpers:
def setUp(self):
super().setUp()
self.target = self.makeTarget()
- self.owner = self.target.owner
+ self.owner = self.get_target_owner()
login_person(self.owner)
+ def get_target_owner(self):
+ return self.target.owner
+
def makeView(self, name, **kwargs):
# XXX cjwatson 2020-02-06: We need to give the view a
# LaunchpadPrincipal rather than just a person, since otherwise bits
@@ -211,6 +253,7 @@ class WebhookTargetViewTestHelpers:
class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
layer = DatabaseFunctionalLayer
+ _webhook_listing = webhook_listing_constants
def makeHooksAndMatchers(self, count):
hooks = [
@@ -256,7 +299,7 @@ class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
self.assertThat(
self.makeView("+webhooks")(),
MatchesAll(
- webhook_listing_constants,
+ self._webhook_listing,
Not(soupmatchers.HTMLContains(webhook_listing_tag)),
),
)
@@ -267,7 +310,7 @@ class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
self.assertThat(
self.makeView("+webhooks")(),
MatchesAll(
- webhook_listing_constants,
+ self._webhook_listing,
soupmatchers.HTMLContains(webhook_listing_tag, *link_matchers),
Not(soupmatchers.HTMLContains(batch_nav_tag)),
),
@@ -279,7 +322,7 @@ class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
self.assertThat(
self.makeView("+webhooks")(),
MatchesAll(
- webhook_listing_constants,
+ self._webhook_listing,
soupmatchers.HTMLContains(
webhook_listing_tag, batch_nav_tag, *link_matchers[:5]
),
@@ -342,6 +385,29 @@ class TestWebhooksViewCharmRecipe(
pass
+class TestWebhooksViewProductBugUpdate(
+ ProductTestHelpers, TestWebhooksViewBase, TestCaseWithFactory
+):
+
+ pass
+
+
+class TestWebhooksViewDistributionBugUpdate(
+ DistributionTestHelpers, TestWebhooksViewBase, TestCaseWithFactory
+):
+
+ pass
+
+
+class TestWebhooksViewDistributionSourcePackageBugUpdate(
+ DistributionSourcePackageTestHelpers,
+ TestWebhooksViewBase,
+ TestCaseWithFactory,
+):
+
+ pass
+
+
class TestWebhookAddViewBase(WebhookTargetViewTestHelpers):
layer = DatabaseFunctionalLayer
@@ -491,16 +557,42 @@ class TestWebhookAddViewCharmRecipe(
pass
+class TestWebhookAddViewProductBugUpdate(
+ ProductTestHelpers, TestWebhookAddViewBase, TestCaseWithFactory
+):
+
+ pass
+
+
+class TestWebhookAddViewDistributionBugUpdate(
+ DistributionTestHelpers, TestWebhookAddViewBase, TestCaseWithFactory
+):
+
+ pass
+
+
+class TestWebhookAddViewDistributionSourcePackageBugUpdate(
+ DistributionSourcePackageTestHelpers,
+ TestWebhookAddViewBase,
+ TestCaseWithFactory,
+):
+
+ pass
+
+
class WebhookViewTestHelpers:
def setUp(self):
super().setUp()
self.target = self.makeTarget()
- self.owner = self.target.owner
+ self.owner = self.get_target_owner()
self.webhook = self.factory.makeWebhook(
target=self.target, delivery_url="http://example.com/original"
)
login_person(self.owner)
+ def get_target_owner(self):
+ return self.target.owner
+
def makeView(self, name, **kwargs):
view = create_view(self.webhook, name, principal=self.owner, **kwargs)
# To test the breadcrumbs we need a correct traversal stack.
@@ -728,3 +820,26 @@ class TestWebhookDeleteViewCharmRecipe(
):
pass
+
+
+class TestWebhookDeleteViewProductBugUpdate(
+ ProductTestHelpers, TestWebhookDeleteViewBase, TestCaseWithFactory
+):
+
+ pass
+
+
+class TestWebhookDeleteViewDistributionBugUpdate(
+ DistributionTestHelpers, TestWebhookDeleteViewBase, TestCaseWithFactory
+):
+
+ pass
+
+
+class TestWebhookDeleteViewDistributionSourcePackageBugUpdate(
+ DistributionSourcePackageTestHelpers,
+ TestWebhookDeleteViewBase,
+ TestCaseWithFactory,
+):
+
+ pass
diff --git a/lib/lp/services/webhooks/tests/test_model.py b/lib/lp/services/webhooks/tests/test_model.py
index 6a128d6..c148e24 100644
--- a/lib/lp/services/webhooks/tests/test_model.py
+++ b/lib/lp/services/webhooks/tests/test_model.py
@@ -560,6 +560,43 @@ class TestWebhookSetBugBase(TestWebhookSetBase):
self.assertEqual(delivery.payload, {"some": "payload"})
+class TestWebhookSetProductBugComment(
+ TestWebhookSetBugBase, TestCaseWithFactory
+):
+
+ event_type = "bug:comment:0.1"
+
+ def makeTarget(self, **kwargs):
+ return self.factory.makeProduct(**kwargs)
+
+
+class TestWebhookSetDistributionBugComment(
+ TestWebhookSetBugBase, TestCaseWithFactory
+):
+
+ event_type = "bug:comment:0.1"
+
+ def makeTarget(self, **kwargs):
+ return self.factory.makeDistribution(**kwargs)
+
+
+class TestWebhookSetDistributionSourcePackageBugComment(
+ TestWebhookSetBugBase, TestCaseWithFactory
+):
+
+ event_type = "bug:comment:0.1"
+
+ def get_target_owner(self, target):
+ return target.distribution.owner
+
+ def makeTarget(self, **kwargs):
+ with admin_logged_in():
+ distribution = self.factory.makeDistribution(**kwargs)
+ return self.factory.makeDistributionSourcePackage(
+ distribution=distribution
+ )
+
+
class TestWebhookSetProductBugUpdate(
TestWebhookSetBugBase, TestCaseWithFactory
):
diff --git a/lib/lp/services/webhooks/tests/test_webservice.py b/lib/lp/services/webhooks/tests/test_webservice.py
index 7f84a14..a6f9c0b 100644
--- a/lib/lp/services/webhooks/tests/test_webservice.py
+++ b/lib/lp/services/webhooks/tests/test_webservice.py
@@ -45,17 +45,20 @@ class TestWebhook(TestCaseWithFactory):
def setUp(self):
super().setUp()
- target = self.factory.makeGitRepository()
- self.owner = target.owner
+ self.target = self.factory.makeGitRepository()
+ self.owner = self.get_target_owner()
with person_logged_in(self.owner):
self.webhook = self.factory.makeWebhook(
- target=target, delivery_url="http://example.com/ep"
+ target=self.target, delivery_url="http://example.com/ep"
)
self.webhook_url = api_url(self.webhook)
self.webservice = webservice_for_person(
self.owner, permission=OAuthPermission.WRITE_PRIVATE
)
+ def get_target_owner(self):
+ return self.target.owner
+
def test_get(self):
representation = self.webservice.get(
self.webhook_url, api_version="devel"
@@ -262,11 +265,11 @@ class TestWebhookDelivery(TestCaseWithFactory):
def setUp(self):
super().setUp()
- target = self.factory.makeGitRepository()
- self.owner = target.owner
+ self.target = self.factory.makeGitRepository()
+ self.owner = self.get_target_owner()
with person_logged_in(self.owner):
self.webhook = self.factory.makeWebhook(
- target=target, delivery_url="http://example.com/ep"
+ target=self.target, delivery_url="http://example.com/ep"
)
self.webhook_url = api_url(self.webhook)
self.delivery = self.webhook.ping()
@@ -275,6 +278,9 @@ class TestWebhookDelivery(TestCaseWithFactory):
self.owner, permission=OAuthPermission.WRITE_PRIVATE
)
+ def get_target_owner(self):
+ return self.target.owner
+
def test_get(self):
representation = self.webservice.get(
self.delivery_url, api_version="devel"
@@ -355,12 +361,15 @@ class TestWebhookTargetBase:
def setUp(self):
super().setUp()
self.target = self.makeTarget()
- self.owner = self.target.owner
+ self.owner = self.get_target_owner()
self.target_url = api_url(self.target)
self.webservice = webservice_for_person(
self.owner, permission=OAuthPermission.WRITE_PRIVATE
)
+ def get_target_owner(self):
+ return self.target.owner
+
def test_webhooks(self):
with person_logged_in(self.owner):
for ep in ("http://example.com/ep1", "http://example.com/ep2"):
@@ -511,3 +520,36 @@ class TestWebhookTargetCharmRecipe(TestWebhookTargetBase, TestCaseWithFactory):
}
):
return self.factory.makeCharmRecipe(registrant=owner, owner=owner)
+
+
+class TestWebhookTargetProduct(TestWebhookTargetBase, TestCaseWithFactory):
+
+ event_type = "bug:0.1"
+
+ def makeTarget(self):
+ owner = self.factory.makePerson()
+ return self.factory.makeProduct(owner=owner)
+
+
+class TestWebhookTargetDistribution(
+ TestWebhookTargetBase, TestCaseWithFactory
+):
+
+ event_type = "bug:0.1"
+
+ def makeTarget(self):
+ owner = self.factory.makePerson()
+ return self.factory.makeDistribution(owner=owner)
+
+
+class TestWebhookTargetDistributionSourcePackage(
+ TestWebhookTargetBase, TestCaseWithFactory
+):
+
+ event_type = "bug:0.1"
+
+ def makeTarget(self):
+ return self.factory.makeDistributionSourcePackage()
+
+ def get_target_owner(self):
+ return self.target.distribution.owner