Skip to content

Commit e07b73c

Browse files
committed
Automation Rules: allow to invert a match
Ref #7653 Closes #6354
1 parent abd3b60 commit e07b73c

File tree

6 files changed

+70
-9
lines changed

6 files changed

+70
-9
lines changed

docs/automation-rules.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ You can change the order using the up and down arrow buttons.
8282

8383
New rules are added at the end (lower priority).
8484

85+
Perform action on match
86+
-----------------------
87+
88+
If this attribute is un-check, the rule will be inverted.
89+
This means the action will be performed over versions that don't match the pattern.
90+
8591
Examples
8692
--------
8793

@@ -127,9 +133,7 @@ Activate all new tags and branches that start with ``v`` or ``V``
127133
Activate all new tags that don't contain the ``-nightly`` suffix
128134
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
129135

130-
.. TODO: update example if https://github.com/readthedocs/readthedocs.org/issues/6354 is approved.
131-
132-
133-
- Custom match: ``.*(?<!-nightly)$``
136+
- Perform action on match: ``False``
137+
- Custom match: ``-nightly$``
134138
- Version type: ``Tag``
135139
- Action: ``Activate version``

readthedocs/builds/forms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class Meta:
110110
model = RegexAutomationRule
111111
fields = [
112112
'description',
113+
'on_match',
113114
'predefined_match_arg',
114115
'match_arg',
115116
'version_type',
@@ -128,6 +129,11 @@ def __init__(self, *args, **kwargs):
128129
self.project = kwargs.pop('project', None)
129130
super().__init__(*args, **kwargs)
130131

132+
# Remove the nullable option from the form.
133+
# TODO: remove after migration.
134+
self.fields['on_match'].widget = forms.CheckboxInput()
135+
self.fields['on_match'].empty_value = False
136+
131137
# Only list supported types
132138
self.fields['version_type'].choices = [
133139
(None, '-' * 9),
@@ -181,6 +187,7 @@ def save(self, commit=True):
181187
rule = RegexAutomationRule.objects.add_rule(
182188
project=self.project,
183189
description=self.cleaned_data['description'],
190+
on_match=self.cleaned_data['on_match'],
184191
match_arg=self.cleaned_data['match_arg'],
185192
predefined_match_arg=self.cleaned_data['predefined_match_arg'],
186193
version_type=self.cleaned_data['version_type'],

readthedocs/builds/managers.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,7 @@ class VersionAutomationRuleManager(PolymorphicManager):
194194
"""
195195

196196
def add_rule(
197-
self, *, project, description, match_arg, version_type,
198-
action, action_arg=None, predefined_match_arg=None,
197+
self, *, project, description, match_arg, version_type, action, **kwargs,
199198
):
200199
"""
201200
Append an automation rule to `project`.
@@ -219,9 +218,8 @@ def add_rule(
219218
priority=priority,
220219
description=description,
221220
match_arg=match_arg,
222-
predefined_match_arg=predefined_match_arg,
223221
version_type=version_type,
224222
action=action,
225-
action_arg=action_arg,
223+
**kwargs,
226224
)
227225
return rule
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.16 on 2020-11-10 15:45
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('builds', '0027_add_privacy_level_automation_rules'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='versionautomationrule',
15+
name='on_match',
16+
field=models.BooleanField(default=True, help_text="Perform this action when the pattern matches or when it doesn't match?", null=True, verbose_name='Perform action on match'),
17+
),
18+
]

readthedocs/builds/models.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,13 @@ class VersionAutomationRule(PolymorphicModel, TimeStampedModel):
965965
related_name='automation_rules',
966966
on_delete=models.CASCADE,
967967
)
968+
on_match = models.BooleanField(
969+
_('Perform action on match?'),
970+
# TODO: remove after migration.
971+
null=True,
972+
default=True,
973+
help_text=_('Perform this action when the pattern matches or when it doesn\'t match?'),
974+
)
968975
priority = models.IntegerField(
969976
_('Rule priority'),
970977
help_text=_('A lower number (0) means a higher priority'),
@@ -1034,7 +1041,8 @@ def run(self, version, *args, **kwargs):
10341041
"""
10351042
if version.type == self.version_type:
10361043
match, result = self.match(version, self.get_match_arg())
1037-
if match:
1044+
# TODO: remove the is None check after migration.
1045+
if bool(match) is (self.on_match is None or self.on_match):
10381046
self.apply_action(version, result)
10391047
return True
10401048
return False

readthedocs/rtd_tests/tests/test_automation_rules.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,32 @@ def test_match(
9292
)
9393
assert rule.run(version) is result
9494

95+
@pytest.mark.parametrize('type', [TAG, BRANCH])
96+
def test_not_match(self, type):
97+
version = get(
98+
Version,
99+
verbose_name='dont-match',
100+
project=self.project,
101+
active=False,
102+
type=type,
103+
built=False,
104+
)
105+
rule = get(
106+
RegexAutomationRule,
107+
project=self.project,
108+
on_match=False,
109+
priority=0,
110+
match_arg='^match$',
111+
action=VersionAutomationRule.ACTIVATE_VERSION_ACTION,
112+
version_type=type,
113+
)
114+
115+
assert rule.run(version) is True
116+
117+
version.verbose_name = 'match'
118+
version.save()
119+
assert rule.run(version) is False
120+
95121
@pytest.mark.parametrize(
96122
'version_name,result',
97123
[

0 commit comments

Comments
 (0)