Skip to content

Commit 0da9bd5

Browse files
authored
feat(admin): set Help Scout URL on malware report (#18477)
1 parent f1820e0 commit 0da9bd5

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed

tests/unit/admin/test_routes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,11 @@ def test_includeme():
385385
"/admin/malware_reports/{observation_id}/",
386386
domain=warehouse,
387387
),
388+
pretend.call(
389+
"admin.malware_reports.detail.add_helpscout_conversation",
390+
"/admin/malware_reports/{observation_id}/add_helpscout_conversation/",
391+
domain=warehouse,
392+
),
388393
pretend.call(
389394
"admin.malware_reports.detail.verdict_not_malware",
390395
"/admin/malware_reports/{observation_id}/not_malware/",

tests/unit/admin/views/test_malware_reports.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from datetime import datetime
44

55
import pretend
6+
import pytest
67

78
from pyramid.httpexceptions import HTTPSeeOther
89

@@ -333,3 +334,57 @@ def test_detail_remove_malware_for_project_updates_all_observations(
333334
queue="success",
334335
),
335336
]
337+
338+
@pytest.mark.parametrize(
339+
"input_url",
340+
[
341+
"https://secure.helpscout.net/conversation/123456789/987654321",
342+
"https://secure.helpscout.net/conversation/123456789/23711?viewId=7857079#thread-9203145731" # noqa: E501
343+
"https://api.helpscout.net/v2/conversations/123456789",
344+
],
345+
)
346+
def test_add_helpscout_conversation_valid_url(self, db_request, input_url):
347+
report = ProjectObservationFactory.create(kind="is_malware")
348+
349+
db_request.matchdict["observation_id"] = str(report.id)
350+
db_request.POST["helpscout_conversation_url"] = input_url
351+
db_request.route_path = lambda r, **kw: f"/admin/malware_reports/{report.id}"
352+
db_request.session = pretend.stub(
353+
flash=pretend.call_recorder(lambda *a, **kw: None)
354+
)
355+
356+
result = views.add_helpscout_conversation(db_request)
357+
358+
assert isinstance(result, HTTPSeeOther)
359+
assert result.headers["Location"] == f"/admin/malware_reports/{report.id}"
360+
assert db_request.session.flash.calls == [
361+
pretend.call(
362+
f"HelpScout Conversation URL added to report for {report.related.name}",
363+
queue="success",
364+
)
365+
]
366+
assert report.additional["helpscout_conversation_url"] == (
367+
"https://api.helpscout.net/v2/conversations/123456789"
368+
)
369+
370+
def test_add_helpscout_conversation_invalid_url(self, db_request):
371+
report = ProjectObservationFactory.create(kind="is_malware")
372+
373+
db_request.matchdict["observation_id"] = str(report.id)
374+
db_request.POST["helpscout_conversation_url"] = "invalid-url"
375+
db_request.route_path = lambda r, **kw: f"/admin/malware_reports/{report.id}"
376+
db_request.session = pretend.stub(
377+
flash=pretend.call_recorder(lambda *a, **kw: None)
378+
)
379+
380+
result = views.add_helpscout_conversation(db_request)
381+
382+
assert isinstance(result, HTTPSeeOther)
383+
assert result.headers["Location"] == f"/admin/malware_reports/{report.id}"
384+
assert db_request.session.flash.calls == [
385+
pretend.call(
386+
"Please provide a valid HelpScout Conversation URL.",
387+
queue="error",
388+
)
389+
]
390+
assert "helpscout_conversation_url" not in report.additional

warehouse/admin/routes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,11 @@ def includeme(config):
395395
"/admin/malware_reports/{observation_id}/",
396396
domain=warehouse,
397397
)
398+
config.add_route(
399+
"admin.malware_reports.detail.add_helpscout_conversation",
400+
"/admin/malware_reports/{observation_id}/add_helpscout_conversation/",
401+
domain=warehouse,
402+
)
398403
config.add_route(
399404
"admin.malware_reports.detail.verdict_not_malware",
400405
"/admin/malware_reports/{observation_id}/not_malware/",

warehouse/admin/templates/admin/malware_reports/detail.html

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,25 @@ <h3 class="card-title">
4848
<dd class="col-sm-10">
4949
{{ report.summary }}
5050
</dd>
51+
<dt class="col-sm-2" title="Help Scout Conversation">HS Convo</dt>
52+
<dd class="col-sm-10">
53+
{% if report.additional.get("helpscout_conversation_url") %}
54+
{% set hs_url = report.additional.helpscout_conversation_url %}
55+
{% set conversation_id = hs_url.rstrip('/').split('/')[-1] %}
56+
<a href="https://secure.helpscout.net/conversation/{{ conversation_id }}" target="_blank">
57+
HelpScout Conversation #{{ conversation_id }}
58+
</a>
59+
<button type="button"
60+
class="btn btn-sm btn-outline-primary"
61+
data-toggle="modal"
62+
data-target="#modal-add-helpscout-conversation">Update</button>
63+
{% else %}
64+
<button type="button"
65+
class="btn btn-sm btn-outline-primary"
66+
data-toggle="modal"
67+
data-target="#modal-add-helpscout-conversation">Add HelpScout Conversation</button>
68+
{% endif %}
69+
</dd>
5170
{# TODO: Decide how to pretty this up when we have more data #}
5271
{% if report.actions %}
5372
<dt class="col-sm-2">Actions</dt>
@@ -221,4 +240,47 @@ <h4 class="modal-title">Remove Malware</h4>
221240
</div>
222241
</div>
223242
<!-- /.modal -->
243+
<div class="modal fade" id="modal-add-helpscout-conversation">
244+
<div class="modal-dialog modal-add-helpscout-conversation">
245+
<form id="add-helpscout-conversation"
246+
action="{{ request.route_path('admin.malware_reports.detail.add_helpscout_conversation', observation_id=report.id) }}"
247+
method="post">
248+
<input name="csrf_token"
249+
type="hidden"
250+
value="{{ request.session.get_csrf_token() }}">
251+
<div class="modal-content">
252+
<div class="modal-header">
253+
<h4 class="modal-title">Add HelpScout Conversation</h4>
254+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
255+
<span aria-hidden="true">×</span>
256+
</button>
257+
</div>
258+
<div class="modal-body">
259+
<p>
260+
Add a HelpScout conversation URL to this Malware Report.
261+
This will allow you to track the conversation related to this report.
262+
</p>
263+
<p>
264+
Paste either the API URL or the web URL.
265+
</p>
266+
<div class="form-group">
267+
<label for="helpscout_conversation_url">HelpScout Conversation URL</label>
268+
<input name="helpscout_conversation_url"
269+
id="helpscout_conversation_url"
270+
class="form-control"
271+
type="url"
272+
placeholder="Enter HelpScout Conversation URL"
273+
value="{{ report.additional.get('helpscout_conversation_url', '') }}"
274+
required>
275+
</div>
276+
</div>
277+
<div class="modal-footer justify-content-between">
278+
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
279+
<button type="submit" class="btn btn-primary">Save</button>
280+
</div>
281+
</div>
282+
</form>
283+
</div>
284+
</div>
285+
<!-- /.modal -->
224286
{% endblock %}

warehouse/admin/views/malware_reports.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"""Admin Views related to Observations"""
44
from __future__ import annotations
55

6+
import re
7+
68
from datetime import datetime, timezone
79
from typing import TYPE_CHECKING
810

@@ -215,6 +217,55 @@ def malware_reports_detail(request):
215217
return {"report": observation}
216218

217219

220+
@view_config(
221+
route_name="admin.malware_reports.detail.add_helpscout_conversation",
222+
permission=Permissions.AdminObservationsWrite,
223+
uses_session=True,
224+
require_csrf=True,
225+
require_methods=["POST"],
226+
)
227+
def add_helpscout_conversation(request):
228+
"""
229+
Add a HelpScout Conversation URL to an Observation.
230+
"""
231+
observation_id = request.matchdict.get("observation_id")
232+
observation = request.db.get(Observation, observation_id)
233+
234+
helpscout_conversation_url = request.POST.get("helpscout_conversation_url")
235+
236+
# Allow the user to paste any kind of Help Scout URL, parse out the conversation ID.
237+
match = re.match(
238+
r"https://(?:secure|api)\.helpscout\.net/(?:conversation|v2/conversations)/(\d+)", # noqa: E501
239+
helpscout_conversation_url,
240+
)
241+
if not match:
242+
request.session.flash(
243+
"Please provide a valid HelpScout Conversation URL.",
244+
queue="error",
245+
)
246+
return HTTPSeeOther(
247+
request.route_path(
248+
"admin.malware_reports.detail", observation_id=observation_id
249+
)
250+
)
251+
252+
conversation_id = match.group(1)
253+
observation.additional["helpscout_conversation_url"] = (
254+
f"https://api.helpscout.net/v2/conversations/{conversation_id}"
255+
)
256+
257+
request.session.flash(
258+
f"HelpScout Conversation URL added to report for {observation.related.name}",
259+
queue="success",
260+
)
261+
262+
return HTTPSeeOther(
263+
request.route_path(
264+
"admin.malware_reports.detail", observation_id=observation_id
265+
)
266+
)
267+
268+
218269
@view_config(
219270
route_name="admin.malware_reports.detail.verdict_not_malware",
220271
permission=Permissions.AdminObservationsWrite,

0 commit comments

Comments
 (0)