diff --git a/README.rst b/README.rst index d2aa4291..0a236781 100644 --- a/README.rst +++ b/README.rst @@ -498,7 +498,8 @@ parameters include: - ``owner`` - ``rebuild_strategy`` - ``scmurl`` -- ``state`` (can be the state name or the state ID e.g. ``state=done``) +- ``state`` (Can be the state name or the state ID e.g. ``state=done``. This + parameter can be given multiple times, in which case or-ing will be used.) - ``state_reason`` - ``stream`` - ``submitted_after`` (Zulu ISO 8601 format e.g. ``submitted_after=2016-08-22T09:40:07Z``) @@ -662,7 +663,9 @@ parameters include: - ``package`` - ``ref`` - ``scmurl`` -- ``state`` +- ``state`` (Can be the state name or the state ID. Koji states are used + for resolving to IDs. This parameter can be given multiple times, in which + case or-ing will be used.) - ``state_reason`` - ``tagged`` (boolean e.g. "true" or "false") - ``tagged_in_final`` (boolean e.g. "true" or "false") diff --git a/module_build_service/utils/views.py b/module_build_service/utils/views.py index 00b46ae5..f73fe68f 100644 --- a/module_build_service/utils/views.py +++ b/module_build_service/utils/views.py @@ -134,6 +134,9 @@ def filter_component_builds(flask_request): """ search_query = dict() for key in request.args.keys(): + # Search by state will be handled separately + if key == 'state': + continue # Only filter on valid database columns if key in models.ComponentBuild.__table__.columns.keys(): if isinstance(models.ComponentBuild.__table__.columns[key].type, sqlalchemy_boolean): @@ -141,10 +144,12 @@ def filter_component_builds(flask_request): else: search_query[key] = flask_request.args[key] - state = flask_request.args.get('state', None) - if state: + # Multiple states can be supplied => or-ing will take place + states = flask_request.args.getlist('state') + search_states = [] + for state in states: if state.isdigit(): - search_query['state'] = state + search_states.append(state) else: try: import koji @@ -152,9 +157,9 @@ def filter_component_builds(flask_request): raise ValidationError('Cannot filter by state names because koji isn\'t installed') if state.upper() in koji.BUILD_STATES: - search_query['state'] = koji.BUILD_STATES[state.upper()] + search_states.append(koji.BUILD_STATES[state.upper()]) else: - raise ValidationError('An invalid state was supplied') + raise ValidationError('Invalid state was supplied: %s' % state) # Allow the user to specify the module build ID with a more intuitive key name if 'module_build' in flask_request.args: @@ -164,6 +169,8 @@ def filter_component_builds(flask_request): if search_query: query = query.filter_by(**search_query) + if search_states: + query = query.filter(models.ComponentBuild.state.in_(search_states)) query = _add_order_by_clause(flask_request, query, models.ComponentBuild) @@ -186,20 +193,24 @@ def filter_module_builds(flask_request): if key not in special_columns and key in models.ModuleBuild.__table__.columns.keys(): search_query[key] = flask_request.args[key] - state = flask_request.args.get('state', None) - if state: + # Multiple states can be supplied => or-ing will take place + states = flask_request.args.getlist('state') + search_states = [] + for state in states: if state.isdigit(): - search_query['state'] = state + search_states.append(state) else: if state in models.BUILD_STATES: - search_query['state'] = models.BUILD_STATES[state] + search_states.append(models.BUILD_STATES[state]) else: - raise ValidationError('An invalid state was supplied') + raise ValidationError('Invalid state was supplied: %s' % state) query = models.ModuleBuild.query if search_query: query = query.filter_by(**search_query) + if search_states: + query = query.filter(models.ModuleBuild.state.in_(search_states)) # This is used when filtering the date request parameters, but it is here to avoid recompiling utc_iso_datetime_regex = re.compile( diff --git a/tests/test_views/test_views.py b/tests/test_views/test_views.py index 2c9adc7b..25db7725 100644 --- a/tests/test_views/test_views.py +++ b/tests/test_views/test_views.py @@ -442,6 +442,16 @@ class TestViews: data = json.loads(rv.data) assert data['meta']['total'] == 1 + def test_query_component_builds_filter_state(self): + rv = self.client.get('/module-build-service/1/component-builds/?state=3') + data = json.loads(rv.data) + assert data['meta']['total'] == 2 + + def test_query_component_builds_filter_multiple_states(self): + rv = self.client.get('/module-build-service/1/component-builds/?state=3&state=1') + data = json.loads(rv.data) + assert data['meta']['total'] == 12 + def test_query_builds_filter_name(self): rv = self.client.get('/module-build-service/1/module-builds/?name=nginx') data = json.loads(rv.data) @@ -500,6 +510,12 @@ class TestViews: data = json.loads(rv.data) assert data['meta']['total'] == 2 + def test_query_builds_filter_multiple_states(self): + rv = self.client.get( + '/module-build-service/1/module-builds/?state=3&state=1') + data = json.loads(rv.data) + assert data['meta']['total'] == 4 + def test_query_builds_two_filters(self): rv = self.client.get('/module-build-service/1/module-builds/?owner=Moe%20Szyslak' '&modified_after=2016-09-03T11:35:00Z')