diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f29da0e3f1..e6ac1bff52 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -24,6 +24,8 @@ Added values - ``succeeded`` (trigger instance matched a rule and action execution was triggered successfully), ``failed`` (trigger instance matched a rule, but it didn't result in an action execution due to Jinja rendering failure or other exception). (improvement) #4134 +* Add trigger type reference based filtering to the ``/v1/triggerinstances`` API endpoint - e.g. + ``/v1/triggerinstances?trigger_type=core.st2.webhook``. (new feature) #4151 Changed ~~~~~~~ diff --git a/st2api/st2api/controllers/resource.py b/st2api/st2api/controllers/resource.py index df1f174219..c2040f5c7d 100644 --- a/st2api/st2api/controllers/resource.py +++ b/st2api/st2api/controllers/resource.py @@ -181,7 +181,13 @@ def _get_all(self, exclude_fields=None, include_fields=None, advanced_filters=No if k in ['id', 'name'] and isinstance(filter_value, list): filters[k + '__in'] = filter_value else: - filters['__'.join(v.split('.'))] = filter_value + field_name_split = v.split('.') + + # Make sure filter value is a list when using "in" filter + if field_name_split[-1] == 'in' and not isinstance(filter_value, (list, tuple)): + filter_value = [filter_value] + + filters['__'.join(field_name_split)] = filter_value if advanced_filters: for token in advanced_filters.split(' '): diff --git a/st2api/st2api/controllers/v1/triggers.py b/st2api/st2api/controllers/v1/triggers.py index 84d2d3700d..847aade2b5 100644 --- a/st2api/st2api/controllers/v1/triggers.py +++ b/st2api/st2api/controllers/v1/triggers.py @@ -359,10 +359,10 @@ class TriggerInstanceController(TriggerInstanceControllerMixin, resource.Resourc the lifecycle of TriggerInstances in the system. """ supported_filters = { - 'trigger': 'trigger', 'timestamp_gt': 'occurrence_time.gt', 'timestamp_lt': 'occurrence_time.lt', - 'status': 'status' + 'status': 'status', + 'trigger': 'trigger.in' } filter_transform_functions = { @@ -393,6 +393,21 @@ def get_all(self, sort=None, offset=0, limit=None, requester_user=None, **raw_fi Handles requests: GET /triggerinstances/ """ + # If trigger_type filter is provided, filter based on the TriggerType via Trigger object + trigger_type_ref = raw_filters.get('trigger_type', None) + + if trigger_type_ref: + # 1. Retrieve TriggerType object id which match this trigger_type ref + trigger_dbs = Trigger.query(type=trigger_type_ref, + only_fields=['ref', 'name', 'pack', 'type']) + trigger_refs = [trigger_db.ref for trigger_db in trigger_dbs] + raw_filters['trigger'] = trigger_refs + + if trigger_type_ref and len(raw_filters.get('trigger', [])) == 0: + # Empty list means trigger_type_ref filter was provided, but we matched no Triggers so + # we should return back empty result + return [] + trigger_instances = self._get_trigger_instances(sort=sort, offset=offset, limit=limit, diff --git a/st2api/tests/unit/controllers/v1/test_triggerinstances.py b/st2api/tests/unit/controllers/v1/test_triggerinstances.py index 814db3ce4a..b796b0a6e3 100644 --- a/st2api/tests/unit/controllers/v1/test_triggerinstances.py +++ b/st2api/tests/unit/controllers/v1/test_triggerinstances.py @@ -73,6 +73,22 @@ def test_get_all_filter_by_timestamp(self): resp = self.app.get('/v1/triggerinstances?timestamp_lt=%s' % (timestamp_middle)) self.assertEqual(len(resp.json), 1) + def test_get_all_trigger_type_ref_filtering(self): + # 1. Invalid / inexistent trigger type ref + resp = self.app.get('/v1/triggerinstances?trigger_type=foo.bar.invalid') + self.assertEqual(resp.status_int, http_client.OK) + self.assertEqual(len(resp.json), 0) + + # 2. Valid trigger type ref with corresponding trigger instances + resp = self.app.get('/v1/triggerinstances?trigger_type=dummy_pack_1.st2.test.triggertype0') + self.assertEqual(resp.status_int, http_client.OK) + self.assertEqual(len(resp.json), 1) + + # 3. Valid trigger type ref with no corresponding trigger instances + resp = self.app.get('/v1/triggerinstances?trigger_type=dummy_pack_1.st2.test.triggertype3') + self.assertEqual(resp.status_int, http_client.OK) + self.assertEqual(len(resp.json), 0) + def test_reemit_trigger_instance(self): resp = self.app.get('/v1/triggerinstances') self.assertEqual(resp.status_int, http_client.OK) @@ -127,9 +143,17 @@ def _setupTriggerTypes(cls): 'payload_schema': {'tp1': None, 'tp2': None, 'tp3': None}, 'parameters_schema': {'param1': {'type': 'object'}} } + TRIGGERTYPE_3 = { + 'name': 'st2.test.triggertype3', + 'pack': 'dummy_pack_1', + 'description': 'test trigger', + 'payload_schema': {'tp1': None, 'tp2': None, 'tp3': None}, + 'parameters_schema': {'param1': {'type': 'object'}} + } cls.app.post_json('/v1/triggertypes', TRIGGERTYPE_0, expect_errors=False) cls.app.post_json('/v1/triggertypes', TRIGGERTYPE_1, expect_errors=False) cls.app.post_json('/v1/triggertypes', TRIGGERTYPE_2, expect_errors=False) + cls.app.post_json('/v1/triggertypes', TRIGGERTYPE_3, expect_errors=False) @classmethod def _setupTriggers(cls): diff --git a/st2common/st2common/openapi.yaml b/st2common/st2common/openapi.yaml index 01235e7aa4..4413ebd1a7 100644 --- a/st2common/st2common/openapi.yaml +++ b/st2common/st2common/openapi.yaml @@ -3918,6 +3918,10 @@ paths: description: Start timestamp greater than filter type: string pattern: ^\d{4}\-\d{2}\-\d{2}(\s|T)\d{2}:\d{2}:\d{2}(\.\d{3,6})?(Z|\+00|\+0000|\+00:00)$ + - name: trigger_type + in: query + description: Trigger type filter + type: string x-parameters: - name: user in: context diff --git a/st2common/st2common/openapi.yaml.j2 b/st2common/st2common/openapi.yaml.j2 index b3ff5d631a..59d5202754 100644 --- a/st2common/st2common/openapi.yaml.j2 +++ b/st2common/st2common/openapi.yaml.j2 @@ -3914,6 +3914,10 @@ paths: description: Start timestamp greater than filter type: string pattern: {{ ISO8601_UTC_REGEX }} + - name: trigger_type + in: query + description: Trigger type filter + type: string x-parameters: - name: user in: context