split appeal job action from abuse job action (#22406)
* split appeal job action from abuse job action * don't localise hidden activity log format labels
This commit is contained in:
Родитель
e7263ae0a6
Коммит
f85937b394
|
@ -1041,8 +1041,18 @@ class AUTO_REJECT_CONTENT_AFTER_DELAY_EXPIRED(_LOG):
|
||||||
|
|
||||||
class RESOLVE_CINDER_JOB_WITH_NO_ACTION(_LOG):
|
class RESOLVE_CINDER_JOB_WITH_NO_ACTION(_LOG):
|
||||||
id = 191
|
id = 191
|
||||||
format = '{addon} cinder job resolved with no action .'
|
format = '{addon} abuse report job resolved with no action.'
|
||||||
short = _('Job Resolved as Ignore/Approve')
|
short = 'Reports resolved as Ignore/Approve'
|
||||||
|
keep = True
|
||||||
|
review_queue = True
|
||||||
|
hide_developer = True
|
||||||
|
reviewer_review_action = True
|
||||||
|
|
||||||
|
|
||||||
|
class DENY_APPEAL_JOB(_LOG):
|
||||||
|
id = 192
|
||||||
|
format = '{addon} appeal job denied.'
|
||||||
|
short = 'Appeal denied'
|
||||||
keep = True
|
keep = True
|
||||||
review_queue = True
|
review_queue = True
|
||||||
hide_developer = True
|
hide_developer = True
|
||||||
|
|
|
@ -177,10 +177,10 @@ class VersionsChoiceWidget(forms.SelectMultiple):
|
||||||
return option
|
return option
|
||||||
|
|
||||||
|
|
||||||
class ReasonsChoiceField(ModelMultipleChoiceField):
|
class WidgetRenderedModelMultipleChoiceField(ModelMultipleChoiceField):
|
||||||
"""
|
"""
|
||||||
Widget to use together with ReasonsChoiceWidget to display checkboxes
|
Field to use together with a suitable Widget subclass to pass down the object to be
|
||||||
with extra data for the canned responses.
|
rendered.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
|
@ -191,14 +191,14 @@ class ReasonsChoiceField(ModelMultipleChoiceField):
|
||||||
|
|
||||||
class ReasonsChoiceWidget(forms.CheckboxSelectMultiple):
|
class ReasonsChoiceWidget(forms.CheckboxSelectMultiple):
|
||||||
"""
|
"""
|
||||||
Widget to use together with ReasonsChoiceField to display checkboxes
|
Widget to use together with a WidgetRenderedModelMultipleChoiceField to display
|
||||||
with extra data for the canned responses.
|
checkboxes with extra data for the canned responses.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def create_option(self, *args, **kwargs):
|
def create_option(self, *args, **kwargs):
|
||||||
option = super().create_option(*args, **kwargs)
|
option = super().create_option(*args, **kwargs)
|
||||||
# label_from_instance() on ReasonsChoiceField returns the full object,
|
# label_from_instance() on WidgetRenderedModelMultipleChoiceField returns the
|
||||||
# not a label, this is what makes this work.
|
# full object, not a label, this is what makes this work.
|
||||||
obj = option['label']
|
obj = option['label']
|
||||||
canned_response = (
|
canned_response = (
|
||||||
obj.cinder_policy.full_text(obj.canned_response)
|
obj.cinder_policy.full_text(obj.canned_response)
|
||||||
|
@ -210,11 +210,24 @@ class ReasonsChoiceWidget(forms.CheckboxSelectMultiple):
|
||||||
return option
|
return option
|
||||||
|
|
||||||
|
|
||||||
class CinderJobChoiceField(ModelMultipleChoiceField):
|
class CinderJobsWidget(forms.CheckboxSelectMultiple):
|
||||||
def label_from_instance(self, obj):
|
"""
|
||||||
|
Widget to use together with a WidgetRenderedModelMultipleChoiceField to display
|
||||||
|
select elements with additional attribute to allow toggling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
option_template_name = 'reviewers/includes/input_option_with_label_attrs.html'
|
||||||
|
|
||||||
|
def create_option(
|
||||||
|
self, name, value, label, selected, index, subindex=None, attrs=None
|
||||||
|
):
|
||||||
|
# label_from_instance() on WidgetRenderedModelMultipleChoiceField returns the
|
||||||
|
# full object, not a label, this is what makes this work.
|
||||||
|
obj = label
|
||||||
is_escalation = (
|
is_escalation = (
|
||||||
obj.decision and obj.decision.action == DECISION_ACTIONS.AMO_ESCALATE_ADDON
|
obj.decision and obj.decision.action == DECISION_ACTIONS.AMO_ESCALATE_ADDON
|
||||||
)
|
)
|
||||||
|
is_appeal = obj.is_appeal
|
||||||
reports = obj.all_abuse_reports
|
reports = obj.all_abuse_reports
|
||||||
reasons_set = {
|
reasons_set = {
|
||||||
(report.REASONS.for_value(report.reason).display,) for report in reports
|
(report.REASONS.for_value(report.reason).display,) for report in reports
|
||||||
|
@ -227,10 +240,10 @@ class CinderJobChoiceField(ModelMultipleChoiceField):
|
||||||
for report in reports
|
for report in reports
|
||||||
)
|
)
|
||||||
subtext = f'Reasoning: {obj.decision.notes}' if is_escalation else ''
|
subtext = f'Reasoning: {obj.decision.notes}' if is_escalation else ''
|
||||||
return format_html(
|
label = format_html(
|
||||||
'{}{}{}<details><summary>Show detail on {} reports</summary>'
|
'{}{}{}<details><summary>Show detail on {} reports</summary>'
|
||||||
'<span>{}</span><ul>{}</ul></details>',
|
'<span>{}</span><ul>{}</ul></details>',
|
||||||
'[Appeal] ' if obj.is_appeal else '',
|
'[Appeal] ' if is_appeal else '',
|
||||||
'[Escalation] ' if is_escalation else '',
|
'[Escalation] ' if is_escalation else '',
|
||||||
format_html_join(', ', '"{}"', reasons_set),
|
format_html_join(', ', '"{}"', reasons_set),
|
||||||
len(reports),
|
len(reports),
|
||||||
|
@ -238,6 +251,14 @@ class CinderJobChoiceField(ModelMultipleChoiceField):
|
||||||
format_html_join('', '<li>{}{}</li>', messages_gen),
|
format_html_join('', '<li>{}{}</li>', messages_gen),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
attrs = attrs or {}
|
||||||
|
attrs['data-value'] = (
|
||||||
|
'resolve_appeal_job' if not is_appeal else 'resolve_reports_job'
|
||||||
|
)
|
||||||
|
return super().create_option(
|
||||||
|
name, value, label, selected, index, subindex, attrs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ActionChoiceWidget(forms.RadioSelect):
|
class ActionChoiceWidget(forms.RadioSelect):
|
||||||
"""
|
"""
|
||||||
|
@ -313,7 +334,7 @@ class ReviewForm(forms.Form):
|
||||||
min_value=1,
|
min_value=1,
|
||||||
max_value=99,
|
max_value=99,
|
||||||
)
|
)
|
||||||
reasons = ReasonsChoiceField(
|
reasons = WidgetRenderedModelMultipleChoiceField(
|
||||||
label='Choose one or more reasons:',
|
label='Choose one or more reasons:',
|
||||||
# queryset is set later in __init__.
|
# queryset is set later in __init__.
|
||||||
queryset=ReviewActionReason.objects.none(),
|
queryset=ReviewActionReason.objects.none(),
|
||||||
|
@ -321,17 +342,25 @@ class ReviewForm(forms.Form):
|
||||||
widget=ReasonsChoiceWidget,
|
widget=ReasonsChoiceWidget,
|
||||||
)
|
)
|
||||||
version_pk = forms.IntegerField(required=False, min_value=1)
|
version_pk = forms.IntegerField(required=False, min_value=1)
|
||||||
resolve_cinder_jobs = CinderJobChoiceField(
|
cinder_jobs_to_resolve = WidgetRenderedModelMultipleChoiceField(
|
||||||
label='Outstanding DSA related reports to resolve:',
|
label='Outstanding DSA related reports to resolve:',
|
||||||
required=False,
|
required=False,
|
||||||
queryset=CinderJob.objects.none(),
|
queryset=CinderJob.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=CinderJobsWidget(attrs={'class': 'data-toggle-hide'}),
|
||||||
)
|
)
|
||||||
|
|
||||||
cinder_policies = forms.ModelMultipleChoiceField(
|
cinder_policies = forms.ModelMultipleChoiceField(
|
||||||
# queryset is set later in __init__
|
# queryset is set later in __init__
|
||||||
queryset=CinderPolicy.objects.none(),
|
queryset=CinderPolicy.objects.none(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Choose one or more policies:',
|
label='Choose one or more policies:',
|
||||||
|
widget=widgets.CheckboxSelectMultiple,
|
||||||
|
)
|
||||||
|
appeal_action = forms.MultipleChoiceField(
|
||||||
|
required=False,
|
||||||
|
label='Choose how to resolve appeal:',
|
||||||
|
choices=(('deny', 'Deny Appeal(s)'),),
|
||||||
|
widget=widgets.CheckboxSelectMultiple,
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
|
@ -344,12 +373,14 @@ class ReviewForm(forms.Form):
|
||||||
self.fields['versions'].required = True
|
self.fields['versions'].required = True
|
||||||
if not action.get('requires_reasons', False):
|
if not action.get('requires_reasons', False):
|
||||||
self.fields['reasons'].required = False
|
self.fields['reasons'].required = False
|
||||||
if self.data.get('resolve_cinder_jobs'):
|
if self.data.get('cinder_jobs_to_resolve'):
|
||||||
# if a cinder job is being resolved we need a review reason or policy
|
# if a cinder job is being resolved we need a review reason or policy
|
||||||
if action.get('allows_reasons'):
|
if action.get('allows_reasons'):
|
||||||
self.fields['reasons'].required = True
|
self.fields['reasons'].required = True
|
||||||
if action.get('allows_policies'):
|
if action.get('allows_policies'):
|
||||||
self.fields['cinder_policies'].required = True
|
self.fields['cinder_policies'].required = True
|
||||||
|
if self.data.get('action') == 'resolve_appeal_job':
|
||||||
|
self.fields['appeal_action'].required = True
|
||||||
result = super().is_valid()
|
result = super().is_valid()
|
||||||
if result:
|
if result:
|
||||||
self.helper.set_data(self.cleaned_data)
|
self.helper.set_data(self.cleaned_data)
|
||||||
|
@ -357,7 +388,22 @@ class ReviewForm(forms.Form):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
if self.cleaned_data.get('resolve_cinder_jobs') and self.cleaned_data.get(
|
# If the user select a different type of job before changing actions there could
|
||||||
|
# be non-appeal jobs selected as cinder_jobs_to_resolve under resolve_appeal_job
|
||||||
|
# action, or appeal jobs under resolve_reports_job action. So filter them out.
|
||||||
|
if self.cleaned_data.get('action') == 'resolve_appeal_job':
|
||||||
|
self.cleaned_data['cinder_jobs_to_resolve'] = [
|
||||||
|
job
|
||||||
|
for job in self.cleaned_data.get('cinder_jobs_to_resolve', ())
|
||||||
|
if job.is_appeal
|
||||||
|
]
|
||||||
|
elif self.cleaned_data.get('action') == 'resolve_reports_job':
|
||||||
|
self.cleaned_data['cinder_jobs_to_resolve'] = [
|
||||||
|
job
|
||||||
|
for job in self.cleaned_data.get('cinder_jobs_to_resolve', ())
|
||||||
|
if not job.is_appeal
|
||||||
|
]
|
||||||
|
if self.cleaned_data.get('cinder_jobs_to_resolve') and self.cleaned_data.get(
|
||||||
'cinder_policies'
|
'cinder_policies'
|
||||||
):
|
):
|
||||||
actions = self.helper.handler.get_cinder_actions_from_policies(
|
actions = self.helper.handler.get_cinder_actions_from_policies(
|
||||||
|
@ -449,12 +495,10 @@ class ReviewForm(forms.Form):
|
||||||
self.fields['action'].widget.actions = self.helper.actions
|
self.fields['action'].widget.actions = self.helper.actions
|
||||||
|
|
||||||
# Set the queryset for cinderjobs to resolve
|
# Set the queryset for cinderjobs to resolve
|
||||||
self.fields['resolve_cinder_jobs'].queryset = (
|
self.fields[
|
||||||
CinderJob.objects.for_addon(self.helper.addon)
|
'cinder_jobs_to_resolve'
|
||||||
.unresolved()
|
].queryset = self.helper.unresolved_cinderjob_qs
|
||||||
.resolvable_in_reviewer_tools()
|
|
||||||
.prefetch_related('abusereport_set', 'appealed_decisions__cinder_job')
|
|
||||||
)
|
|
||||||
# Set the queryset for policies to show as options
|
# Set the queryset for policies to show as options
|
||||||
self.fields['cinder_policies'].queryset = CinderPolicy.objects.filter(
|
self.fields['cinder_policies'].queryset = CinderPolicy.objects.filter(
|
||||||
expose_in_reviewer_tools=True
|
expose_in_reviewer_tools=True
|
||||||
|
|
|
@ -85,7 +85,7 @@ class Command(BaseCommand):
|
||||||
).distinct()
|
).distinct()
|
||||||
helper.handler.data = {
|
helper.handler.data = {
|
||||||
'comments': log_details.get('comments', ''),
|
'comments': log_details.get('comments', ''),
|
||||||
'resolve_cinder_jobs': cinder_jobs,
|
'cinder_jobs_to_resolve': cinder_jobs,
|
||||||
'versions': versions,
|
'versions': versions,
|
||||||
}
|
}
|
||||||
helper.handler.reject_multiple_versions()
|
helper.handler.reject_multiple_versions()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %} {% include "django/forms/widgets/attrs.html" %}>{% include "django/forms/widgets/input.html" %} {{ widget.label }}</label>
|
|
@ -180,28 +180,38 @@
|
||||||
<div class="review-actions-reasons-select">
|
<div class="review-actions-reasons-select">
|
||||||
{{ form.reasons }}
|
{{ form.reasons }}
|
||||||
</div>
|
</div>
|
||||||
<div class="review-actions-reasons-error">
|
{% if form.reasons.errors %}
|
||||||
{{ form.reasons.errors }}
|
<div class="review-actions-reasons-error">{{ form.reasons.errors }}</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="data-toggle review-actions-policies"
|
<div class="data-toggle review-actions-policies"
|
||||||
data-value="{{ actions_policies|join(' ') }}">
|
data-value="{{ actions_policies|join(' ') }}">
|
||||||
<label for="id_cinder_policies">{{ form.cinder_policies.label }}</label>
|
<label for="id_cinder_policies">{{ form.cinder_policies.label }}</label>
|
||||||
<div class="review-actions-policies-select">
|
<div class="review-actions-policies-select">
|
||||||
{{ form.cinder_policies }}
|
{{ form.cinder_policies }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ form.cinder_policies.errors }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="data-toggle"
|
||||||
{{ form.cinder_policies.errors }}
|
data-value="resolve_appeal_job">
|
||||||
|
<label for="id_appeal_action">{{ form.appeal_action.label }}</label>
|
||||||
|
<div class="review-actions-policies-select">
|
||||||
|
{{ form.appeal_action }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ form.appeal_action.errors }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="review-actions-section data-toggle review-resolve-abuse-reports"
|
<div class="review-actions-section data-toggle review-resolve-abuse-reports"
|
||||||
data-value="{{ actions_resolves_abuse_reports|join(' ') }}">
|
data-value="{{ actions_resolves_abuse_reports|join(' ') }}">
|
||||||
<strong><label for="id_resolve_cinder_jobs">{{ form.resolve_cinder_jobs.label }}</label></strong>
|
<strong><label for="id_cinder_jobs_to_resolve">{{ form.cinder_jobs_to_resolve.label }}</label></strong>
|
||||||
{{ form.resolve_cinder_jobs }}
|
{{ form.cinder_jobs_to_resolve }}
|
||||||
{{ form.resolve_cinder_jobs.errors }}
|
{{ form.cinder_jobs_to_resolve.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="review-actions-section review-actions-files data-toggle review-files"
|
<div class="review-actions-section review-actions-files data-toggle review-files"
|
||||||
|
|
|
@ -356,7 +356,7 @@ class TestReviewForm(TestCase):
|
||||||
reason = ReviewActionReason.objects.create(name='A reason', canned_response='a')
|
reason = ReviewActionReason.objects.create(name='A reason', canned_response='a')
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
assert not form.is_bound
|
assert not form.is_bound
|
||||||
data = {'action': 'public', 'comments': 'lol', 'resolve_cinder_jobs': [job]}
|
data = {'action': 'public', 'comments': 'lol', 'cinder_jobs_to_resolve': [job]}
|
||||||
form = self.get_form(data=data)
|
form = self.get_form(data=data)
|
||||||
assert form.is_bound
|
assert form.is_bound
|
||||||
assert not form.is_valid()
|
assert not form.is_valid()
|
||||||
|
@ -384,9 +384,8 @@ class TestReviewForm(TestCase):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
assert not form.is_bound
|
assert not form.is_bound
|
||||||
data = {
|
data = {
|
||||||
'action': 'resolve_job',
|
'action': 'resolve_reports_job',
|
||||||
'comments': 'lol',
|
'cinder_jobs_to_resolve': [job.id],
|
||||||
'resolve_cinder_jobs': [job.id],
|
|
||||||
}
|
}
|
||||||
form = self.get_form(data=data)
|
form = self.get_form(data=data)
|
||||||
assert form.is_bound
|
assert form.is_bound
|
||||||
|
@ -399,6 +398,34 @@ class TestReviewForm(TestCase):
|
||||||
assert form.is_valid(), form.errors
|
assert form.is_valid(), form.errors
|
||||||
assert not form.errors
|
assert not form.errors
|
||||||
|
|
||||||
|
def test_appeal_action_require_with_resolve_appeal_job(self):
|
||||||
|
self.grant_permission(self.request.user, 'Addons:Review')
|
||||||
|
self.addon.update(status=amo.STATUS_NOMINATED)
|
||||||
|
self.version.file.update(status=amo.STATUS_AWAITING_REVIEW)
|
||||||
|
job = CinderJob.objects.create(
|
||||||
|
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
|
||||||
|
)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=job, addon=self.addon, action=DECISION_ACTIONS.AMO_DISABLE_ADDON
|
||||||
|
)
|
||||||
|
form = self.get_form()
|
||||||
|
assert not form.is_bound
|
||||||
|
data = {
|
||||||
|
'action': 'resolve_appeal_job',
|
||||||
|
'comments': 'lol',
|
||||||
|
'cinder_jobs_to_resolve': [job.id],
|
||||||
|
}
|
||||||
|
form = self.get_form(data=data)
|
||||||
|
assert form.is_bound
|
||||||
|
assert not form.is_valid()
|
||||||
|
assert form.errors == {'appeal_action': ['This field is required.']}
|
||||||
|
|
||||||
|
data['appeal_action'] = ['deny']
|
||||||
|
form = self.get_form(data=data)
|
||||||
|
assert form.is_bound
|
||||||
|
assert form.is_valid(), form.errors
|
||||||
|
assert not form.errors
|
||||||
|
|
||||||
def test_only_one_cinder_action_selected(self):
|
def test_only_one_cinder_action_selected(self):
|
||||||
self.grant_permission(self.request.user, 'Addons:Review')
|
self.grant_permission(self.request.user, 'Addons:Review')
|
||||||
self.addon.update(status=amo.STATUS_NOMINATED)
|
self.addon.update(status=amo.STATUS_NOMINATED)
|
||||||
|
@ -430,9 +457,8 @@ class TestReviewForm(TestCase):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
assert not form.is_bound
|
assert not form.is_bound
|
||||||
data = {
|
data = {
|
||||||
'action': 'resolve_job',
|
'action': 'resolve_reports_job',
|
||||||
'comments': 'lol',
|
'cinder_jobs_to_resolve': [job.id],
|
||||||
'resolve_cinder_jobs': [job.id],
|
|
||||||
'cinder_policies': [no_action_policy.id],
|
'cinder_policies': [no_action_policy.id],
|
||||||
}
|
}
|
||||||
form = self.get_form(data=data)
|
form = self.get_form(data=data)
|
||||||
|
@ -453,6 +479,58 @@ class TestReviewForm(TestCase):
|
||||||
assert form.is_valid()
|
assert form.is_valid()
|
||||||
assert not form.errors
|
assert not form.errors
|
||||||
|
|
||||||
|
def test_cinder_jobs_filtered_for_resolve_reports_job_and_resolve_appeal_job(self):
|
||||||
|
self.grant_permission(self.request.user, 'Addons:Review')
|
||||||
|
self.addon.update(status=amo.STATUS_NOMINATED)
|
||||||
|
self.version.file.update(status=amo.STATUS_AWAITING_REVIEW)
|
||||||
|
appeal_job = CinderJob.objects.create(
|
||||||
|
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
|
||||||
|
)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=appeal_job,
|
||||||
|
addon=self.addon,
|
||||||
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
|
)
|
||||||
|
report_job = CinderJob.objects.create(
|
||||||
|
job_id='2', resolvable_in_reviewer_tools=True, target_addon=self.addon
|
||||||
|
)
|
||||||
|
AbuseReport.objects.create(cinder_job=report_job, guid=self.addon.guid)
|
||||||
|
policy = CinderPolicy.objects.create(
|
||||||
|
uuid='a',
|
||||||
|
name='ignore',
|
||||||
|
expose_in_reviewer_tools=True,
|
||||||
|
default_cinder_action=DECISION_ACTIONS.AMO_IGNORE,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'action': 'resolve_appeal_job',
|
||||||
|
'comments': 'lol',
|
||||||
|
'appeal_action': ['deny'],
|
||||||
|
'cinder_jobs_to_resolve': [report_job.id],
|
||||||
|
}
|
||||||
|
form = self.get_form(data=data)
|
||||||
|
form.is_valid()
|
||||||
|
assert form.cleaned_data['cinder_jobs_to_resolve'] == []
|
||||||
|
|
||||||
|
data['cinder_jobs_to_resolve'] = [report_job, appeal_job]
|
||||||
|
form = self.get_form(data=data)
|
||||||
|
form.is_valid()
|
||||||
|
assert form.cleaned_data['cinder_jobs_to_resolve'] == [appeal_job]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'action': 'resolve_reports_job',
|
||||||
|
'cinder_policies': [policy.id],
|
||||||
|
'cinder_jobs_to_resolve': [appeal_job.id],
|
||||||
|
}
|
||||||
|
form = self.get_form(data=data)
|
||||||
|
form.is_valid()
|
||||||
|
assert form.cleaned_data['cinder_jobs_to_resolve'] == []
|
||||||
|
|
||||||
|
data['cinder_jobs_to_resolve'] = [report_job.id, appeal_job.id]
|
||||||
|
form = self.get_form(data=data)
|
||||||
|
form.is_valid()
|
||||||
|
assert form.cleaned_data['cinder_jobs_to_resolve'] == [report_job]
|
||||||
|
|
||||||
def test_boilerplate(self):
|
def test_boilerplate(self):
|
||||||
self.grant_permission(self.request.user, 'Addons:Review')
|
self.grant_permission(self.request.user, 'Addons:Review')
|
||||||
self.addon.update(status=amo.STATUS_NOMINATED)
|
self.addon.update(status=amo.STATUS_NOMINATED)
|
||||||
|
@ -898,7 +976,7 @@ class TestReviewForm(TestCase):
|
||||||
form = self.get_form(data={**data, 'version_pk': self.version.pk})
|
form = self.get_form(data={**data, 'version_pk': self.version.pk})
|
||||||
assert form.is_valid(), form.errors
|
assert form.is_valid(), form.errors
|
||||||
|
|
||||||
def test_resolve_cinder_jobs_choices(self):
|
def test_cinder_jobs_to_resolve_choices(self):
|
||||||
abuse_kw = {
|
abuse_kw = {
|
||||||
'guid': self.addon.guid,
|
'guid': self.addon.guid,
|
||||||
'location': AbuseReport.LOCATION.ADDON,
|
'location': AbuseReport.LOCATION.ADDON,
|
||||||
|
@ -977,7 +1055,7 @@ class TestReviewForm(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
choices = form.fields['resolve_cinder_jobs'].choices
|
choices = form.fields['cinder_jobs_to_resolve'].choices
|
||||||
qs_list = list(choices.queryset)
|
qs_list = list(choices.queryset)
|
||||||
assert qs_list == [
|
assert qs_list == [
|
||||||
# Only unresolved, reviewer handled, jobs are shown
|
# Only unresolved, reviewer handled, jobs are shown
|
||||||
|
@ -986,9 +1064,10 @@ class TestReviewForm(TestCase):
|
||||||
cinder_job_2_reports,
|
cinder_job_2_reports,
|
||||||
]
|
]
|
||||||
|
|
||||||
content = str(form['resolve_cinder_jobs'])
|
content = str(form['cinder_jobs_to_resolve'])
|
||||||
doc = pq(content)
|
doc = pq(content)
|
||||||
assert doc('label[for="id_resolve_cinder_jobs_0"]').text() == (
|
label_0 = doc('label[for="id_cinder_jobs_to_resolve_0"]')
|
||||||
|
assert label_0.text() == (
|
||||||
'[Escalation] "DSA: It violates Mozilla\'s Add-on Policies"\n'
|
'[Escalation] "DSA: It violates Mozilla\'s Add-on Policies"\n'
|
||||||
'Show detail on 1 reports\n'
|
'Show detail on 1 reports\n'
|
||||||
'Reasoning: Why o why\n'
|
'Reasoning: Why o why\n'
|
||||||
|
@ -996,15 +1075,24 @@ class TestReviewForm(TestCase):
|
||||||
)
|
)
|
||||||
assert '<script>alert()</script>' not in content # should be escaped
|
assert '<script>alert()</script>' not in content # should be escaped
|
||||||
assert '<script>alert()</script>' in content # should be escaped
|
assert '<script>alert()</script>' in content # should be escaped
|
||||||
assert doc('label[for="id_resolve_cinder_jobs_1"]').text() == (
|
label_1 = doc('label[for="id_cinder_jobs_to_resolve_1"]')
|
||||||
|
assert label_1.text() == (
|
||||||
'[Appeal] "DSA: It violates Mozilla\'s Add-on Policies"'
|
'[Appeal] "DSA: It violates Mozilla\'s Add-on Policies"'
|
||||||
'\nShow detail on 1 reports\nv[1.2]: ccc'
|
'\nShow detail on 1 reports\nv[1.2]: ccc'
|
||||||
)
|
)
|
||||||
assert doc('label[for="id_resolve_cinder_jobs_2"]').text() == (
|
label_2 = doc('label[for="id_cinder_jobs_to_resolve_2"]')
|
||||||
|
assert label_2.text() == (
|
||||||
'"DSA: It violates Mozilla\'s Add-on Policies"\n'
|
'"DSA: It violates Mozilla\'s Add-on Policies"\n'
|
||||||
'Show detail on 2 reports\n<no message>\nbbb'
|
'Show detail on 2 reports\n<no message>\nbbb'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert label_0.attr['class'] == 'data-toggle-hide'
|
||||||
|
assert label_0.attr['data-value'] == 'resolve_appeal_job'
|
||||||
|
assert label_1.attr['class'] == 'data-toggle-hide'
|
||||||
|
assert label_1.attr['data-value'] == 'resolve_reports_job'
|
||||||
|
assert label_2.attr['class'] == 'data-toggle-hide'
|
||||||
|
assert label_2.attr['data-value'] == 'resolve_appeal_job'
|
||||||
|
|
||||||
def test_cinder_policies_choices(self):
|
def test_cinder_policies_choices(self):
|
||||||
policy_exposed = CinderPolicy.objects.create(
|
policy_exposed = CinderPolicy.objects.create(
|
||||||
uuid='1', name='foo', expose_in_reviewer_tools=True
|
uuid='1', name='foo', expose_in_reviewer_tools=True
|
||||||
|
|
|
@ -949,16 +949,34 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
)
|
)
|
||||||
assert expected == actions
|
assert expected == actions
|
||||||
|
|
||||||
def test_actions_resolve_cinder_jobs(self):
|
def test_actions_cinder_jobs_to_resolve(self):
|
||||||
self.grant_permission(self.user, 'Addons:Review')
|
self.grant_permission(self.user, 'Addons:Review')
|
||||||
CinderJob.objects.create(
|
job = CinderJob.objects.create(
|
||||||
target_addon=self.addon, resolvable_in_reviewer_tools=True
|
target_addon=self.addon, resolvable_in_reviewer_tools=True
|
||||||
)
|
)
|
||||||
expected = [
|
expected = [
|
||||||
'reject_multiple_versions',
|
'reject_multiple_versions',
|
||||||
'set_needs_human_review_multiple_versions',
|
'set_needs_human_review_multiple_versions',
|
||||||
'reply',
|
'reply',
|
||||||
'resolve_job',
|
'resolve_reports_job',
|
||||||
|
'comment',
|
||||||
|
]
|
||||||
|
actions = list(
|
||||||
|
self.get_review_actions(
|
||||||
|
addon_status=amo.STATUS_APPROVED,
|
||||||
|
file_status=amo.STATUS_APPROVED,
|
||||||
|
).keys()
|
||||||
|
)
|
||||||
|
assert expected == actions
|
||||||
|
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON, addon=self.addon, appeal_job=job
|
||||||
|
)
|
||||||
|
expected = [
|
||||||
|
'reject_multiple_versions',
|
||||||
|
'set_needs_human_review_multiple_versions',
|
||||||
|
'reply',
|
||||||
|
'resolve_appeal_job',
|
||||||
'comment',
|
'comment',
|
||||||
]
|
]
|
||||||
actions = list(
|
actions = list(
|
||||||
|
@ -1049,10 +1067,12 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
uuid='z', default_cinder_action=DECISION_ACTIONS.AMO_IGNORE
|
uuid='z', default_cinder_action=DECISION_ACTIONS.AMO_IGNORE
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
'resolve_cinder_jobs': [cinder_job],
|
'cinder_jobs_to_resolve': [cinder_job],
|
||||||
}
|
}
|
||||||
self.helper.set_data(data)
|
self.helper.set_data(data)
|
||||||
self.helper.handler.review_action = self.helper.actions.get('resolve_job')
|
self.helper.handler.review_action = self.helper.actions.get(
|
||||||
|
'resolve_reports_job'
|
||||||
|
)
|
||||||
self.helper.handler.log_action(amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION)
|
self.helper.handler.log_action(amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION)
|
||||||
assert ReviewActionReasonLog.objects.count() == 0
|
assert ReviewActionReasonLog.objects.count() == 0
|
||||||
assert CinderPolicyLog.objects.count() == 2
|
assert CinderPolicyLog.objects.count() == 2
|
||||||
|
@ -1063,6 +1083,71 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
== 'AMO_IGNORE'
|
== 'AMO_IGNORE'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_log_action_override_policies(self):
|
||||||
|
self.grant_permission(self.user, 'Addons:Review')
|
||||||
|
cinder_job = CinderJob.objects.create(
|
||||||
|
target_addon=self.addon, resolvable_in_reviewer_tools=True
|
||||||
|
)
|
||||||
|
other_policy = CinderPolicy.objects.create(
|
||||||
|
uuid='y', default_cinder_action=DECISION_ACTIONS.AMO_DISABLE_ADDON
|
||||||
|
)
|
||||||
|
self.helper = self.get_helper()
|
||||||
|
data = {
|
||||||
|
'cinder_policies': [
|
||||||
|
CinderPolicy.objects.create(
|
||||||
|
uuid='z', default_cinder_action=DECISION_ACTIONS.AMO_IGNORE
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'cinder_jobs_to_resolve': [cinder_job],
|
||||||
|
}
|
||||||
|
self.helper.set_data(data)
|
||||||
|
self.helper.handler.review_action = self.helper.actions.get(
|
||||||
|
'resolve_reports_job'
|
||||||
|
)
|
||||||
|
self.helper.handler.log_action(
|
||||||
|
amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION, policies=[other_policy]
|
||||||
|
)
|
||||||
|
assert ReviewActionReasonLog.objects.count() == 0
|
||||||
|
assert CinderPolicyLog.objects.count() == 1
|
||||||
|
assert CinderPolicyLog.objects.first().cinder_policy == other_policy
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.get(
|
||||||
|
action=amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION.id
|
||||||
|
).details['cinder_action']
|
||||||
|
== 'AMO_DISABLE_ADDON' # from other_policy
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_log_action_override_action(self):
|
||||||
|
self.grant_permission(self.user, 'Addons:Review')
|
||||||
|
cinder_job = CinderJob.objects.create(
|
||||||
|
target_addon=self.addon, resolvable_in_reviewer_tools=True
|
||||||
|
)
|
||||||
|
self.helper = self.get_helper()
|
||||||
|
data = {
|
||||||
|
'cinder_policies': [
|
||||||
|
CinderPolicy.objects.create(
|
||||||
|
uuid='z', default_cinder_action=DECISION_ACTIONS.AMO_IGNORE
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'cinder_jobs_to_resolve': [cinder_job],
|
||||||
|
}
|
||||||
|
self.helper.set_data(data)
|
||||||
|
self.helper.handler.review_action = self.helper.actions.get(
|
||||||
|
'resolve_reports_job'
|
||||||
|
)
|
||||||
|
self.helper.handler.log_action(
|
||||||
|
amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION,
|
||||||
|
cinder_action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
|
)
|
||||||
|
assert ReviewActionReasonLog.objects.count() == 0
|
||||||
|
assert CinderPolicyLog.objects.count() == 1
|
||||||
|
assert (
|
||||||
|
ActivityLog.objects.get(
|
||||||
|
action=amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION.id
|
||||||
|
).details['cinder_action']
|
||||||
|
== 'AMO_DISABLE_ADDON'
|
||||||
|
)
|
||||||
|
|
||||||
def test_log_action_override_user(self):
|
def test_log_action_override_user(self):
|
||||||
# ActivityLog.user will default to self.user in log_action.
|
# ActivityLog.user will default to self.user in log_action.
|
||||||
self.helper.set_data(self.get_data())
|
self.helper.set_data(self.get_data())
|
||||||
|
@ -1089,16 +1174,16 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
cinder_job1 = CinderJob.objects.create(job_id='1')
|
cinder_job1 = CinderJob.objects.create(job_id='1')
|
||||||
cinder_job2 = CinderJob.objects.create(job_id='2')
|
cinder_job2 = CinderJob.objects.create(job_id='2')
|
||||||
|
|
||||||
# without 'resolve_cinder_jobs', notify_addon_decision_to_cinder is called
|
# without 'cinder_jobs_to_resolve', notify_addon_decision_to_cinder is called
|
||||||
self.helper.set_data(self.get_data())
|
self.helper.set_data(self.get_data())
|
||||||
self.helper.handler.notify_decision()
|
self.helper.handler.notify_decision()
|
||||||
mock_report.assert_called_once_with(
|
mock_report.assert_called_once_with(
|
||||||
addon_id=self.addon.id, log_entry_id=log_entry.id
|
addon_id=self.addon.id, log_entry_id=log_entry.id
|
||||||
)
|
)
|
||||||
|
|
||||||
# with 'resolve_cinder_jobs', resolve_job_in_cinder is called instead
|
# with 'cinder_jobs_to_resolve', resolve_job_in_cinder is called instead
|
||||||
self.helper.set_data(
|
self.helper.set_data(
|
||||||
{**self.get_data(), 'resolve_cinder_jobs': [cinder_job1, cinder_job2]}
|
{**self.get_data(), 'cinder_jobs_to_resolve': [cinder_job1, cinder_job2]}
|
||||||
)
|
)
|
||||||
self.helper.handler.notify_decision()
|
self.helper.handler.notify_decision()
|
||||||
|
|
||||||
|
@ -2249,7 +2334,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
f'{settings.CINDER_SERVER_URL}jobs/1/decision',
|
f'{settings.CINDER_SERVER_URL}jobs/1/decision',
|
||||||
callback=lambda r: (201, {}, json.dumps({'uuid': uuid.uuid4().hex})),
|
callback=lambda r: (201, {}, json.dumps({'uuid': uuid.uuid4().hex})),
|
||||||
)
|
)
|
||||||
self._test_reject_multiple_versions({'resolve_cinder_jobs': [cinder_job]})
|
self._test_reject_multiple_versions({'cinder_jobs_to_resolve': [cinder_job]})
|
||||||
message = mail.outbox[0]
|
message = mail.outbox[0]
|
||||||
self.check_subject(message)
|
self.check_subject(message)
|
||||||
assert 'Extension Delicious Bookmarks was manually reviewed' in message.body
|
assert 'Extension Delicious Bookmarks was manually reviewed' in message.body
|
||||||
|
@ -2345,7 +2430,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
callback=lambda r: (201, {}, json.dumps({'uuid': uuid.uuid4().hex})),
|
callback=lambda r: (201, {}, json.dumps({'uuid': uuid.uuid4().hex})),
|
||||||
)
|
)
|
||||||
self._test_reject_multiple_versions_with_delay(
|
self._test_reject_multiple_versions_with_delay(
|
||||||
{'resolve_cinder_jobs': [cinder_job]}
|
{'cinder_jobs_to_resolve': [cinder_job]}
|
||||||
)
|
)
|
||||||
message = mail.outbox[0]
|
message = mail.outbox[0]
|
||||||
self.check_subject(message)
|
self.check_subject(message)
|
||||||
|
@ -3299,7 +3384,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
|
|
||||||
self.helper.handler.data = {
|
self.helper.handler.data = {
|
||||||
'versions': [self.review_version],
|
'versions': [self.review_version],
|
||||||
'resolve_cinder_jobs': [CinderJob()],
|
'cinder_jobs_to_resolve': [CinderJob()],
|
||||||
}
|
}
|
||||||
resolves_actions = {
|
resolves_actions = {
|
||||||
key: action
|
key: action
|
||||||
|
@ -3366,7 +3451,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
'should_email': True,
|
'should_email': True,
|
||||||
'cinder_action': DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
'cinder_action': DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
},
|
},
|
||||||
'resolve_job': {'should_email': False, 'cinder_action': None},
|
'resolve_reports_job': {'should_email': False, 'cinder_action': None},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.setup_data(amo.STATUS_APPROVED, file_status=amo.STATUS_DISABLED)
|
self.setup_data(amo.STATUS_APPROVED, file_status=amo.STATUS_DISABLED)
|
||||||
|
@ -3385,7 +3470,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
'should_email': True,
|
'should_email': True,
|
||||||
'cinder_action': DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
'cinder_action': DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
},
|
},
|
||||||
'resolve_job': {'should_email': False, 'cinder_action': None},
|
'resolve_reports_job': {'should_email': False, 'cinder_action': None},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.setup_data(amo.STATUS_DISABLED, file_status=amo.STATUS_DISABLED)
|
self.setup_data(amo.STATUS_DISABLED, file_status=amo.STATUS_DISABLED)
|
||||||
|
@ -3399,7 +3484,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
'should_email': True,
|
'should_email': True,
|
||||||
'cinder_action': DECISION_ACTIONS.AMO_APPROVE_VERSION,
|
'cinder_action': DECISION_ACTIONS.AMO_APPROVE_VERSION,
|
||||||
},
|
},
|
||||||
'resolve_job': {'should_email': False, 'cinder_action': None},
|
'resolve_reports_job': {'should_email': False, 'cinder_action': None},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3436,7 +3521,7 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
'should_email': True,
|
'should_email': True,
|
||||||
'cinder_action': DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
'cinder_action': DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
},
|
},
|
||||||
'resolve_job': {'should_email': False, 'cinder_action': None},
|
'resolve_reports_job': {'should_email': False, 'cinder_action': None},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.setup_data(amo.STATUS_DISABLED, file_status=amo.STATUS_DISABLED)
|
self.setup_data(amo.STATUS_DISABLED, file_status=amo.STATUS_DISABLED)
|
||||||
|
@ -3458,10 +3543,74 @@ class TestReviewHelper(TestReviewHelperBase):
|
||||||
'should_email': True,
|
'should_email': True,
|
||||||
'cinder_action': DECISION_ACTIONS.AMO_APPROVE_VERSION,
|
'cinder_action': DECISION_ACTIONS.AMO_APPROVE_VERSION,
|
||||||
},
|
},
|
||||||
'resolve_job': {'should_email': False, 'cinder_action': None},
|
'resolve_reports_job': {'should_email': False, 'cinder_action': None},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_resolve_appeal_job(self):
|
||||||
|
policy_a = CinderPolicy.objects.create(uuid='a')
|
||||||
|
policy_b = CinderPolicy.objects.create(uuid='b')
|
||||||
|
policy_c = CinderPolicy.objects.create(uuid='c')
|
||||||
|
policy_d = CinderPolicy.objects.create(uuid='d')
|
||||||
|
|
||||||
|
appeal_job1 = CinderJob.objects.create(
|
||||||
|
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
|
||||||
|
)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=appeal_job1,
|
||||||
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
|
addon=self.addon,
|
||||||
|
).policies.add(policy_a, policy_b)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=appeal_job1,
|
||||||
|
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,
|
||||||
|
addon=self.addon,
|
||||||
|
).policies.add(policy_a, policy_c)
|
||||||
|
responses.add_callback(
|
||||||
|
responses.POST,
|
||||||
|
f'{settings.CINDER_SERVER_URL}jobs/{appeal_job1.job_id}/decision',
|
||||||
|
callback=lambda r: (201, {}, json.dumps({'uuid': uuid.uuid4().hex})),
|
||||||
|
)
|
||||||
|
|
||||||
|
appeal_job2 = CinderJob.objects.create(
|
||||||
|
job_id='2', resolvable_in_reviewer_tools=True, target_addon=self.addon
|
||||||
|
)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=appeal_job2,
|
||||||
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
|
addon=self.addon,
|
||||||
|
).policies.add(policy_d)
|
||||||
|
responses.add_callback(
|
||||||
|
responses.POST,
|
||||||
|
f'{settings.CINDER_SERVER_URL}jobs/{appeal_job2.job_id}/decision',
|
||||||
|
callback=lambda r: (201, {}, json.dumps({'uuid': uuid.uuid4().hex})),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.grant_permission(self.user, 'Addons:Review')
|
||||||
|
self.file.update(status=amo.STATUS_AWAITING_REVIEW)
|
||||||
|
self.helper = self.get_helper()
|
||||||
|
data = {
|
||||||
|
'comments': 'Nope',
|
||||||
|
'cinder_jobs_to_resolve': [appeal_job1, appeal_job2],
|
||||||
|
'appeal_action': ['deny'],
|
||||||
|
}
|
||||||
|
self.helper.set_data(data)
|
||||||
|
|
||||||
|
self.helper.handler.resolve_appeal_job()
|
||||||
|
|
||||||
|
assert CinderPolicyLog.objects.count() == 4
|
||||||
|
activity_log_qs = ActivityLog.objects.filter(action=amo.LOG.DENY_APPEAL_JOB.id)
|
||||||
|
assert activity_log_qs.count() == 2
|
||||||
|
log2, log1 = list(activity_log_qs.all())
|
||||||
|
assert log1.details['cinder_action'] == 'AMO_DISABLE_ADDON'
|
||||||
|
assert log2.details['cinder_action'] == 'AMO_APPROVE'
|
||||||
|
assert set(appeal_job1.reload().decision.policies.all()) == {
|
||||||
|
policy_a,
|
||||||
|
policy_b,
|
||||||
|
policy_c,
|
||||||
|
}
|
||||||
|
assert set(appeal_job2.reload().decision.policies.all()) == {policy_d}
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ENABLE_ADDON_SIGNING=True)
|
@override_settings(ENABLE_ADDON_SIGNING=True)
|
||||||
class TestReviewHelperSigning(TestReviewHelperBase):
|
class TestReviewHelperSigning(TestReviewHelperBase):
|
||||||
|
|
|
@ -25,7 +25,7 @@ from rest_framework.test import APIRequestFactory
|
||||||
from waffle.testutils import override_switch
|
from waffle.testutils import override_switch
|
||||||
|
|
||||||
from olympia import amo, core, ratings
|
from olympia import amo, core, ratings
|
||||||
from olympia.abuse.models import AbuseReport, CinderJob, CinderPolicy
|
from olympia.abuse.models import AbuseReport, CinderDecision, CinderJob, CinderPolicy
|
||||||
from olympia.access import acl
|
from olympia.access import acl
|
||||||
from olympia.access.models import Group, GroupUser
|
from olympia.access.models import Group, GroupUser
|
||||||
from olympia.accounts.serializers import BaseUserSerializer
|
from olympia.accounts.serializers import BaseUserSerializer
|
||||||
|
@ -2515,7 +2515,7 @@ class TestReview(ReviewBase):
|
||||||
assert ActivityLog.objects.filter(action=comment_version.id).count() == 1
|
assert ActivityLog.objects.filter(action=comment_version.id).count() == 1
|
||||||
|
|
||||||
@mock.patch('olympia.reviewers.utils.resolve_job_in_cinder.delay')
|
@mock.patch('olympia.reviewers.utils.resolve_job_in_cinder.delay')
|
||||||
def test_resolve_cinder_job(self, resolve_mock):
|
def test_resolve_reports_job(self, resolve_mock):
|
||||||
cinder_job = CinderJob.objects.create(
|
cinder_job = CinderJob.objects.create(
|
||||||
job_id='123', target_addon=self.addon, resolvable_in_reviewer_tools=True
|
job_id='123', target_addon=self.addon, resolvable_in_reviewer_tools=True
|
||||||
)
|
)
|
||||||
|
@ -2534,8 +2534,8 @@ class TestReview(ReviewBase):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
{
|
{
|
||||||
'action': 'resolve_job',
|
'action': 'resolve_reports_job',
|
||||||
'resolve_cinder_jobs': [cinder_job.id],
|
'cinder_jobs_to_resolve': [cinder_job.id],
|
||||||
'cinder_policies': [policy.id],
|
'cinder_policies': [policy.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -2548,6 +2548,49 @@ class TestReview(ReviewBase):
|
||||||
assert activity_log_qs[0].details['cinder_action'] == 'AMO_IGNORE'
|
assert activity_log_qs[0].details['cinder_action'] == 'AMO_IGNORE'
|
||||||
resolve_mock.assert_called_once()
|
resolve_mock.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch('olympia.reviewers.utils.resolve_job_in_cinder.delay')
|
||||||
|
def test_resolve_appeal_job(self, resolve_mock):
|
||||||
|
appeal_job1 = CinderJob.objects.create(
|
||||||
|
job_id='1', resolvable_in_reviewer_tools=True, target_addon=self.addon
|
||||||
|
)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=appeal_job1,
|
||||||
|
action=DECISION_ACTIONS.AMO_DISABLE_ADDON,
|
||||||
|
addon=self.addon,
|
||||||
|
)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=appeal_job1,
|
||||||
|
action=DECISION_ACTIONS.AMO_REJECT_VERSION_ADDON,
|
||||||
|
addon=self.addon,
|
||||||
|
)
|
||||||
|
|
||||||
|
appeal_job2 = CinderJob.objects.create(
|
||||||
|
job_id='2', resolvable_in_reviewer_tools=True, target_addon=self.addon
|
||||||
|
)
|
||||||
|
CinderDecision.objects.create(
|
||||||
|
appeal_job=appeal_job2,
|
||||||
|
action=DECISION_ACTIONS.AMO_APPROVE,
|
||||||
|
addon=self.addon,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
self.url,
|
||||||
|
{
|
||||||
|
'action': 'resolve_appeal_job',
|
||||||
|
'comments': 'Nope',
|
||||||
|
'cinder_jobs_to_resolve': [appeal_job1.id, appeal_job2.id],
|
||||||
|
'appeal_action': ['deny'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
|
||||||
|
activity_log_qs = ActivityLog.objects.filter(action=amo.LOG.DENY_APPEAL_JOB.id)
|
||||||
|
assert activity_log_qs.count() == 2
|
||||||
|
log1, log2 = list(activity_log_qs.all())
|
||||||
|
assert log1.details['cinder_action'] == 'AMO_DISABLE_ADDON'
|
||||||
|
assert log2.details['cinder_action'] == 'AMO_APPROVE'
|
||||||
|
assert resolve_mock.call_count == 2
|
||||||
|
|
||||||
def test_reviewer_reply(self):
|
def test_reviewer_reply(self):
|
||||||
reason = ReviewActionReason.objects.create(
|
reason = ReviewActionReason.objects.create(
|
||||||
name='reason 1', is_active=True, canned_response='reason'
|
name='reason 1', is_active=True, canned_response='reason'
|
||||||
|
@ -5634,7 +5677,7 @@ class TestReview(ReviewBase):
|
||||||
self.get_dict(
|
self.get_dict(
|
||||||
action='disable_addon',
|
action='disable_addon',
|
||||||
reasons=[reason.id],
|
reasons=[reason.id],
|
||||||
resolve_cinder_jobs=[cinder_job.id],
|
cinder_jobs_to_resolve=[cinder_job.id],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert self.get_addon().status == amo.STATUS_DISABLED
|
assert self.get_addon().status == amo.STATUS_DISABLED
|
||||||
|
@ -5679,7 +5722,7 @@ class TestReview(ReviewBase):
|
||||||
self.get_dict(
|
self.get_dict(
|
||||||
action='public',
|
action='public',
|
||||||
reasons=[reason.id],
|
reasons=[reason.id],
|
||||||
resolve_cinder_jobs=[cinder_job.id],
|
cinder_jobs_to_resolve=[cinder_job.id],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import markupsafe
|
||||||
|
|
||||||
import olympia.core.logger
|
import olympia.core.logger
|
||||||
from olympia import amo
|
from olympia import amo
|
||||||
from olympia.abuse.models import CinderJob
|
from olympia.abuse.models import CinderJob, CinderPolicy
|
||||||
from olympia.abuse.tasks import notify_addon_decision_to_cinder, resolve_job_in_cinder
|
from olympia.abuse.tasks import notify_addon_decision_to_cinder, resolve_job_in_cinder
|
||||||
from olympia.access import acl
|
from olympia.access import acl
|
||||||
from olympia.activity.models import ActivityLog
|
from olympia.activity.models import ActivityLog
|
||||||
|
@ -537,11 +537,18 @@ class ReviewHelper:
|
||||||
)
|
)
|
||||||
version_is_blocked = self.version and self.version.is_blocked
|
version_is_blocked = self.version and self.version.is_blocked
|
||||||
|
|
||||||
has_unresolved_abuse_report_jobs = (
|
self.unresolved_cinderjob_qs = (
|
||||||
CinderJob.objects.for_addon(self.addon)
|
CinderJob.objects.for_addon(self.addon)
|
||||||
.unresolved()
|
.unresolved()
|
||||||
.resolvable_in_reviewer_tools()
|
.resolvable_in_reviewer_tools()
|
||||||
.exists()
|
.prefetch_related('abusereport_set', 'appealed_decisions__cinder_job')
|
||||||
|
)
|
||||||
|
unresolved_cinder_jobs = list(self.unresolved_cinderjob_qs)
|
||||||
|
has_unresolved_abuse_report_jobs = any(
|
||||||
|
job for job in unresolved_cinder_jobs if not job.is_appeal
|
||||||
|
)
|
||||||
|
has_unresolved_appeal_jobs = any(
|
||||||
|
job for job in unresolved_cinder_jobs if job.is_appeal
|
||||||
)
|
)
|
||||||
|
|
||||||
# Special logic for availability of reject/approve multiple action:
|
# Special logic for availability of reject/approve multiple action:
|
||||||
|
@ -836,9 +843,9 @@ class ReviewHelper:
|
||||||
'requires_reasons': not is_static_theme,
|
'requires_reasons': not is_static_theme,
|
||||||
'resolves_abuse_reports': True,
|
'resolves_abuse_reports': True,
|
||||||
}
|
}
|
||||||
actions['resolve_job'] = {
|
actions['resolve_reports_job'] = {
|
||||||
'method': self.handler.resolve_job,
|
'method': self.handler.resolve_reports_job,
|
||||||
'label': 'Resolve Jobs',
|
'label': 'Resolve Reports',
|
||||||
'details': (
|
'details': (
|
||||||
'Allows abuse report jobs to be resovled without an action on the '
|
'Allows abuse report jobs to be resovled without an action on the '
|
||||||
'add-on or versions.'
|
'add-on or versions.'
|
||||||
|
@ -849,6 +856,18 @@ class ReviewHelper:
|
||||||
'resolves_abuse_reports': True,
|
'resolves_abuse_reports': True,
|
||||||
'allows_policies': True,
|
'allows_policies': True,
|
||||||
}
|
}
|
||||||
|
actions['resolve_appeal_job'] = {
|
||||||
|
'method': self.handler.resolve_appeal_job,
|
||||||
|
'label': 'Resolve Appeals',
|
||||||
|
'details': (
|
||||||
|
'Allows abuse report jobs to be resovled without an action on the '
|
||||||
|
'add-on or versions.'
|
||||||
|
),
|
||||||
|
'minimal': True,
|
||||||
|
'available': is_reviewer and has_unresolved_appeal_jobs,
|
||||||
|
'comments': True,
|
||||||
|
'resolves_abuse_reports': True,
|
||||||
|
}
|
||||||
actions['comment'] = {
|
actions['comment'] = {
|
||||||
'method': self.handler.process_comment,
|
'method': self.handler.process_comment,
|
||||||
'label': 'Comment',
|
'label': 'Comment',
|
||||||
|
@ -943,7 +962,7 @@ class ReviewBase:
|
||||||
self.addon.promotedaddon.approve_for_version(version)
|
self.addon.promotedaddon.approve_for_version(version)
|
||||||
|
|
||||||
def notify_decision(self):
|
def notify_decision(self):
|
||||||
if cinder_jobs := self.data.get('resolve_cinder_jobs', ()):
|
if cinder_jobs := self.data.get('cinder_jobs_to_resolve', ()):
|
||||||
# with appeals and escalations there could be multiple jobs
|
# with appeals and escalations there could be multiple jobs
|
||||||
for cinder_job in cinder_jobs:
|
for cinder_job in cinder_jobs:
|
||||||
resolve_job_in_cinder.delay(
|
resolve_job_in_cinder.delay(
|
||||||
|
@ -1017,29 +1036,31 @@ class ReviewBase:
|
||||||
timestamp=None,
|
timestamp=None,
|
||||||
user=None,
|
user=None,
|
||||||
extra_details=None,
|
extra_details=None,
|
||||||
|
policies=None,
|
||||||
|
cinder_action=None,
|
||||||
):
|
):
|
||||||
cinder_action = getattr(action, 'cinder_action', None)
|
reasons = (
|
||||||
if self.review_action and self.review_action.get('allows_reasons'):
|
self.data.get('reasons', [])
|
||||||
reasons = self.data.get('reasons', [])
|
if self.review_action and self.review_action.get('allows_reasons')
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
if policies is None:
|
||||||
policies = [
|
policies = [
|
||||||
reason.cinder_policy
|
reason.cinder_policy
|
||||||
for reason in reasons
|
for reason in reasons
|
||||||
if getattr(reason, 'cinder_policy', None)
|
if getattr(reason, 'cinder_policy', None)
|
||||||
]
|
]
|
||||||
else:
|
if self.review_action and self.review_action.get('allows_policies'):
|
||||||
reasons = []
|
policies.extend(self.data.get('cinder_policies', []))
|
||||||
policies = []
|
|
||||||
if self.review_action and self.review_action.get('allows_policies'):
|
cinder_action = cinder_action or getattr(action, 'cinder_action', None)
|
||||||
policies.extend(self.data.get('cinder_policies', []))
|
if not cinder_action and policies:
|
||||||
cinder_action = (
|
cinder_action = (
|
||||||
# If there isn't a cinder_action from the activity action already, get
|
# If there isn't a cinder_action from the activity action already, get
|
||||||
# it from the policy. There should only be one in the list as form
|
# it from the policy. There should only be one in the list as form
|
||||||
# validation raises for multiple cinder actions.
|
# validation raises for multiple cinder actions.
|
||||||
cinder_action
|
(actions := self.get_cinder_actions_from_policies(policies))
|
||||||
or (
|
and actions[0]
|
||||||
(actions := self.get_cinder_actions_from_policies(policies))
|
|
||||||
and actions[0]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
details = {
|
details = {
|
||||||
|
@ -1097,11 +1118,33 @@ class ReviewBase:
|
||||||
def process_comment(self):
|
def process_comment(self):
|
||||||
self.log_action(amo.LOG.COMMENT_VERSION)
|
self.log_action(amo.LOG.COMMENT_VERSION)
|
||||||
|
|
||||||
def resolve_job(self):
|
def resolve_reports_job(self):
|
||||||
if self.data.get('resolve_cinder_jobs', ()):
|
if self.data.get('cinder_jobs_to_resolve', ()):
|
||||||
self.log_action(amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION)
|
self.log_action(amo.LOG.RESOLVE_CINDER_JOB_WITH_NO_ACTION)
|
||||||
self.notify_decision() # notify cinder
|
self.notify_decision() # notify cinder
|
||||||
|
|
||||||
|
def resolve_appeal_job(self):
|
||||||
|
# It's possible to have multiple appeal jobs, so handle them seperately.
|
||||||
|
for job in self.data.get('cinder_jobs_to_resolve', ()):
|
||||||
|
# collect all the policies we made decisions under
|
||||||
|
previous_policies = CinderPolicy.objects.filter(
|
||||||
|
cinderdecision__appeal_job=job
|
||||||
|
).distinct()
|
||||||
|
# we just need a single action for this appeal
|
||||||
|
# - use min() to favor AMO_DISABLE_ADDON over AMO_REJECT_VERSION_ADDON
|
||||||
|
previous_action_id = min(
|
||||||
|
decision.action for decision in job.appealed_decisions.all()
|
||||||
|
)
|
||||||
|
self.log_action(
|
||||||
|
amo.LOG.DENY_APPEAL_JOB,
|
||||||
|
policies=list(previous_policies),
|
||||||
|
cinder_action=DECISION_ACTIONS.for_value(previous_action_id),
|
||||||
|
)
|
||||||
|
# notify cinder
|
||||||
|
resolve_job_in_cinder.delay(
|
||||||
|
cinder_job_id=job.id, log_entry_id=self.log_entry.id
|
||||||
|
)
|
||||||
|
|
||||||
def approve_latest_version(self):
|
def approve_latest_version(self):
|
||||||
"""Approve the add-on latest version (potentially setting the add-on to
|
"""Approve the add-on latest version (potentially setting the add-on to
|
||||||
approved if it was awaiting its first review)."""
|
approved if it was awaiting its first review)."""
|
||||||
|
|
|
@ -1050,7 +1050,7 @@ form.review-form .data-toggle {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-actions-section #id_resolve_cinder_jobs {
|
.review-actions-section #id_cinder_jobs_to_resolve {
|
||||||
details {
|
details {
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ function initReviewActions() {
|
||||||
var $element = $(element),
|
var $element = $(element),
|
||||||
value = $element.find('input').val(),
|
value = $element.find('input').val(),
|
||||||
$data_toggle = $('form.review-form').find('.data-toggle'),
|
$data_toggle = $('form.review-form').find('.data-toggle'),
|
||||||
|
$data_toggle_hide = $('form.review-form').find('.data-toggle-hide'),
|
||||||
$comments = $('#id_comments'),
|
$comments = $('#id_comments'),
|
||||||
boilerplate_text = $element.find('input').attr('data-value');
|
boilerplate_text = $element.find('input').attr('data-value');
|
||||||
|
|
||||||
|
@ -87,6 +88,10 @@ function initReviewActions() {
|
||||||
// Hide everything, then show the ones containing the value we're interested in.
|
// Hide everything, then show the ones containing the value we're interested in.
|
||||||
$data_toggle.hide();
|
$data_toggle.hide();
|
||||||
$data_toggle.filter('[data-value~="' + value + '"]').show();
|
$data_toggle.filter('[data-value~="' + value + '"]').show();
|
||||||
|
// For data_toggle_hide, the opposite - show everything, then hide the ones containing
|
||||||
|
// the value we're interested in.
|
||||||
|
$data_toggle_hide.show();
|
||||||
|
$data_toggle_hide.filter('[data-value~="' + value + '"]').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#review-actions .action_nav #id_action > *:not(.disabled)').click(
|
$('#review-actions .action_nav #id_action > *:not(.disabled)').click(
|
||||||
|
|
Загрузка…
Ссылка в новой задаче