Merge lp:~maxiberta/launchpad/lp-1729580 into lp:launchpad

Proposed by Maximiliano Bertacchini
Status: Merged
Merged at revision: 18556
Proposed branch: lp:~maxiberta/launchpad/lp-1729580
Merge into: lp:launchpad
Diff against target: 262 lines (+117/-7)
9 files modified
lib/lp/snappy/browser/tests/test_snapbuild.py (+53/-1)
lib/lp/snappy/interfaces/snapbuild.py (+12/-0)
lib/lp/snappy/interfaces/snapstoreclient.py (+3/-1)
lib/lp/snappy/model/snapbuild.py (+5/-0)
lib/lp/snappy/model/snapbuildjob.py (+11/-0)
lib/lp/snappy/model/snapstoreclient.py (+8/-1)
lib/lp/snappy/templates/snapbuild-index.pt (+12/-1)
lib/lp/snappy/tests/test_snapbuildjob.py (+12/-3)
lib/lp/snappy/tests/test_snapstoreclient.py (+1/-0)
To merge this branch: bzr merge lp:~maxiberta/launchpad/lp-1729580
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+337825@code.launchpad.net

Commit message

Expose extended error messages (with external link) for snap build jobs (LP: #1729580).

Description of the change

Expose extended error messages (with external link) for snap build jobs (LP: #1729580).

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/snappy/browser/tests/test_snapbuild.py'
2--- lib/lp/snappy/browser/tests/test_snapbuild.py 2017-10-20 13:35:42 +0000
3+++ lib/lp/snappy/browser/tests/test_snapbuild.py 2018-02-19 17:53:06 +0000
4@@ -14,7 +14,10 @@
5 from pymacaroons import Macaroon
6 import soupmatchers
7 from storm.locals import Store
8-from testtools.matchers import StartsWith
9+from testtools.matchers import (
10+ Not,
11+ StartsWith,
12+ )
13 import transaction
14 from zope.component import getUtility
15 from zope.security.interfaces import Unauthorized
16@@ -130,6 +133,55 @@
17 text=re.compile(
18 r"^\s*Store upload failed:\s+Scan failed.\s*$"))))
19
20+ def test_store_upload_status_failed_with_extended_error_message(self):
21+ build = self.factory.makeSnapBuild(status=BuildStatus.FULLYBUILT)
22+ job = getUtility(ISnapStoreUploadJobSource).create(build)
23+ naked_job = removeSecurityProxy(job)
24+ naked_job.job._status = JobStatus.FAILED
25+ naked_job.error_message = "This should not be shown."
26+ naked_job.error_messages = [
27+ {"message": "Scan failed.", "link": "link1"},
28+ {"message": "Classic not allowed.", "link": "link2"}]
29+ build_view = create_initialized_view(build, "+index")
30+ built_view = build_view()
31+ self.assertThat(built_view, Not(soupmatchers.HTMLContains(
32+ soupmatchers.Tag(
33+ "store upload status", "li",
34+ attrs={"id": "store-upload-status"},
35+ text=re.compile('.*This should not be shown.*')))))
36+ self.assertThat(built_view, soupmatchers.HTMLContains(
37+ soupmatchers.Within(
38+ soupmatchers.Tag(
39+ "store upload status", "li",
40+ attrs={"id": "store-upload-status"}),
41+ soupmatchers.Tag(
42+ "store upload error messages", "ul",
43+ attrs={"id": "store-upload-error-messages"}))))
44+ self.assertThat(built_view, soupmatchers.HTMLContains(
45+ soupmatchers.Within(
46+ soupmatchers.Tag(
47+ "store upload error messages", "ul",
48+ attrs={"id": "store-upload-error-messages"}),
49+ soupmatchers.Within(
50+ soupmatchers.Tag(
51+ "store upload error message", "li",
52+ text=re.compile(".*Scan failed\..*")),
53+ soupmatchers.Tag(
54+ "store upload error link", "a",
55+ attrs={"href": "link1"}, text="(?)")))))
56+ self.assertThat(built_view, soupmatchers.HTMLContains(
57+ soupmatchers.Within(
58+ soupmatchers.Tag(
59+ "store upload error messages", "ul",
60+ attrs={"id": "store-upload-error-messages"}),
61+ soupmatchers.Within(
62+ soupmatchers.Tag(
63+ "store upload error message", "li",
64+ text=re.compile(".*Classic not allowed\..*")),
65+ soupmatchers.Tag(
66+ "store upload error link", "a",
67+ attrs={"href": "link2"}, text="(?)")))))
68+
69 def test_store_upload_status_release_failed(self):
70 build = self.factory.makeSnapBuild(status=BuildStatus.FULLYBUILT)
71 job = getUtility(ISnapStoreUploadJobSource).create(build)
72
73=== modified file 'lib/lp/snappy/interfaces/snapbuild.py'
74--- lib/lp/snappy/interfaces/snapbuild.py 2017-04-27 16:22:37 +0000
75+++ lib/lp/snappy/interfaces/snapbuild.py 2018-02-19 17:53:06 +0000
76@@ -39,7 +39,9 @@
77 Bool,
78 Choice,
79 Datetime,
80+ Dict,
81 Int,
82+ List,
83 TextLine,
84 )
85
86@@ -213,6 +215,16 @@
87 "this snap build to the store."),
88 required=False, readonly=True))
89
90+ store_upload_error_messages = exported(List(
91+ title=_("Store upload error messages"),
92+ description=_(
93+ "A list of dict(message, link) where message is an error "
94+ "description and link, if any, is an external link to extra "
95+ "details, from the last attempt to upload this snap build "
96+ "to the store."),
97+ value_type=Dict(key_type=TextLine()),
98+ required=False, readonly=True))
99+
100 def getFiles():
101 """Retrieve the build's `ISnapFile` records.
102
103
104=== modified file 'lib/lp/snappy/interfaces/snapstoreclient.py'
105--- lib/lp/snappy/interfaces/snapstoreclient.py 2017-06-14 10:25:23 +0000
106+++ lib/lp/snappy/interfaces/snapstoreclient.py 2018-02-19 17:53:06 +0000
107@@ -29,10 +29,12 @@
108
109 class SnapStoreError(Exception):
110
111- def __init__(self, message="", detail=None, can_retry=False):
112+ def __init__(
113+ self, message="", detail=None, messages=None, can_retry=False):
114 super(SnapStoreError, self).__init__(message)
115 self.message = message
116 self.detail = detail
117+ self.messages = messages
118 self.can_retry = can_retry
119
120
121
122=== modified file 'lib/lp/snappy/model/snapbuild.py'
123--- lib/lp/snappy/model/snapbuild.py 2018-01-23 10:59:44 +0000
124+++ lib/lp/snappy/model/snapbuild.py 2018-02-19 17:53:06 +0000
125@@ -456,6 +456,11 @@
126 job = self.last_store_upload_job
127 return job and job.error_message
128
129+ @property
130+ def store_upload_error_messages(self):
131+ job = self.last_store_upload_job
132+ return job and job.error_messages or []
133+
134 def scheduleStoreUpload(self):
135 """See `ISnapBuild`."""
136 if not self.snap.can_upload_to_store:
137
138=== modified file 'lib/lp/snappy/model/snapbuildjob.py'
139--- lib/lp/snappy/model/snapbuildjob.py 2017-06-29 16:58:01 +0000
140+++ lib/lp/snappy/model/snapbuildjob.py 2018-02-19 17:53:06 +0000
141@@ -232,6 +232,16 @@
142 self.metadata["error_detail"] = detail
143
144 @property
145+ def error_messages(self):
146+ """See `ISnapStoreUploadJob`."""
147+ return self.metadata.get("error_messages")
148+
149+ @error_messages.setter
150+ def error_messages(self, messages):
151+ """See `ISnapStoreUploadJob`."""
152+ self.metadata["error_messages"] = messages
153+
154+ @property
155 def store_url(self):
156 """See `ISnapStoreUploadJob`."""
157 return self.metadata.get("store_url")
158@@ -326,6 +336,7 @@
159 self.attempt_count <= self.max_retries):
160 raise RetryableSnapStoreError(e.message, detail=e.detail)
161 self.error_message = str(e)
162+ self.error_messages = getattr(e, "messages", None)
163 self.error_detail = getattr(e, "detail", None)
164 if isinstance(e, UnauthorizedUploadResponse):
165 mailer = SnapBuildMailer.forUnauthorizedUpload(self.snapbuild)
166
167=== modified file 'lib/lp/snappy/model/snapstoreclient.py'
168--- lib/lp/snappy/model/snapstoreclient.py 2017-06-14 10:25:23 +0000
169+++ lib/lp/snappy/model/snapstoreclient.py 2018-02-19 17:53:06 +0000
170@@ -316,7 +316,14 @@
171 elif "errors" in response_data:
172 error_message = "\n".join(
173 error["message"] for error in response_data["errors"])
174- raise ScanFailedResponse(error_message)
175+ error_messages = []
176+ for error in response_data["errors"]:
177+ error_detail = {"message": error["message"]}
178+ if "link" in error:
179+ error_detail["link"] = error["link"]
180+ error_messages.append(error_detail)
181+ raise ScanFailedResponse(
182+ error_message, messages=error_messages)
183 elif not response_data["can_release"]:
184 return response_data["url"], None
185 else:
186
187=== modified file 'lib/lp/snappy/templates/snapbuild-index.pt'
188--- lib/lp/snappy/templates/snapbuild-index.pt 2017-04-03 12:35:03 +0000
189+++ lib/lp/snappy/templates/snapbuild-index.pt 2018-02-19 17:53:06 +0000
190@@ -177,7 +177,18 @@
191 <tal:failed-upload
192 condition="context/store_upload_status/enumvalue:FAILEDTOUPLOAD">
193 Store upload failed:
194- <tal:error-message replace="context/store_upload_error_message" />
195+ <tal:error-message
196+ condition="not: context/store_upload_error_messages"
197+ replace="context/store_upload_error_message" />
198+ <ul id="store-upload-error-messages">
199+ <li tal:repeat="error context/store_upload_error_messages">
200+ <span tal:replace="error/message"/>
201+ <a tal:condition="error/link"
202+ tal:attributes="href error/link"
203+ target="help"
204+ class="sprite maybe action-icon">(?)</a>
205+ </li>
206+ </ul>
207 <form action="" method="POST">
208 <input type="submit" name="field.actions.upload" value="Retry" />
209 </form>
210
211=== modified file 'lib/lp/snappy/tests/test_snapbuildjob.py'
212--- lib/lp/snappy/tests/test_snapbuildjob.py 2017-06-29 18:06:03 +0000
213+++ lib/lp/snappy/tests/test_snapbuildjob.py 2018-02-19 17:53:06 +0000
214@@ -31,9 +31,9 @@
215 )
216 from lp.snappy.interfaces.snapstoreclient import (
217 BadRefreshResponse,
218- BadScanStatusResponse,
219 ISnapStoreClient,
220 ReleaseFailedResponse,
221+ ScanFailedResponse,
222 UnauthorizedUploadResponse,
223 UploadFailedResponse,
224 UploadNotScannedYetResponse,
225@@ -433,7 +433,11 @@
226 job = SnapStoreUploadJob.create(snapbuild)
227 client = FakeSnapStoreClient()
228 client.upload.result = self.status_url
229- client.checkStatus.failure = BadScanStatusResponse("Scan failed.")
230+ client.checkStatus.failure = ScanFailedResponse(
231+ "Scan failed.\nConfinement not allowed.",
232+ messages=[
233+ {"message": "Scan failed.", "link": "link1"},
234+ {"message": "Confinement not allowed.", "link": "link2"}])
235 self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient))
236 with dbuser(config.ISnapStoreUploadJobSource.dbuser):
237 JobRunner([job]).runAll()
238@@ -443,7 +447,12 @@
239 self.assertContentEqual([job], snapbuild.store_upload_jobs)
240 self.assertIsNone(job.store_url)
241 self.assertIsNone(job.store_revision)
242- self.assertEqual("Scan failed.", job.error_message)
243+ self.assertEqual(
244+ "Scan failed.\nConfinement not allowed.", job.error_message)
245+ self.assertEqual([
246+ {"message": "Scan failed.", "link": "link1"},
247+ {"message": "Confinement not allowed.", "link": "link2"}],
248+ job.error_messages)
249 [notification] = pop_notifications()
250 self.assertEqual(
251 config.canonical.noreply_from_address, notification["From"])
252
253=== modified file 'lib/lp/snappy/tests/test_snapstoreclient.py'
254--- lib/lp/snappy/tests/test_snapstoreclient.py 2017-06-14 10:25:23 +0000
255+++ lib/lp/snappy/tests/test_snapstoreclient.py 2018-02-19 17:53:06 +0000
256@@ -581,6 +581,7 @@
257 "errors": [
258 {"code": None,
259 "message": "You cannot use that reserved namespace.",
260+ "link": "http://example.com"
261 }],
262 }}
263