From a51e30f0a2e8ecd27ac02d5015422091cdec990a Mon Sep 17 00:00:00 2001 From: Bryan Strassner Date: Fri, 13 Apr 2018 18:02:27 -0500 Subject: [PATCH] [ui] Add count of messages to commit configdocs CLI The list of validations returned from commit configdocs can be long and sometimes lose sight of errors. This change adds a summary count of the severity level of messages returned at the bottom of the output so as to clue the user into what might be closer to the top. Change-Id: I2a8921b22977b8ceab0afb1341b195c12dc49c67 --- shipyard_client/cli/format_utils.py | 34 ++++++--- .../tests/unit/cli/test_format_utils.py | 70 ++++++++++++++++++- .../control/test_shipyard_request_context.py | 1 - 3 files changed, 94 insertions(+), 11 deletions(-) diff --git a/shipyard_client/cli/format_utils.py b/shipyard_client/cli/format_utils.py index 4e4b0bca..47bb093f 100644 --- a/shipyard_client/cli/format_utils.py +++ b/shipyard_client/cli/format_utils.py @@ -61,12 +61,21 @@ def cli_format_status_handler(response, is_error=False): resp_j = response.json() resp = formatted.format(resp_j.get('message', 'Not specified'), resp_j.get('reason', 'Not specified')) + # lvl_counts must have a matching number of values as the + # _LEVEL_KEYS below + 1 for sentinel. + lvl_counts = [0, 0, 0, 0] if resp_j.get('details'): mlist = resp_j['details'].get('messageList', []) - for message in sorted(mlist, - key=lambda m: _lvl_key( - m.get('level'), - m.get('error', False))): + # Decorate messages with level number and sortkey + for message in mlist: + message['lnum'], message['sortkey'] = _lvl_key( + message.get('level'), + message.get('error', False) + ) + # Sort and formulate the messages + for message in sorted(mlist, key=lambda m: m['sortkey']): + lnum = message['lnum'] + lvl_counts[lnum] = lvl_counts[lnum] + 1 if message.get('kind') == 'ValidationMessage': resp = resp + _format_validation_message(message) else: @@ -76,6 +85,11 @@ def cli_format_status_handler(response, is_error=False): _INDENT, message['source'] ) + # Append a count summary + resp = resp + ("\n\n#### Errors: {}," + " Warnings: {}," + " Infos: {}," + " Other: {} ####".format(*lvl_counts)) return resp else: return '' @@ -92,14 +106,18 @@ _LEVEL_KEYS = { 1: ['warn', 'warning'], 2: ['info', 'debug'], } -_SENTINEL_LEVEL = "999" +_SENTINEL_SORT_KEY = "3none" +_SENTINEL_LEVEL = 3 def _lvl_key(level_name, error): - """Generate a level key value + """Generate a level key value and sort key Returns a value to support sort order based on lvls dict. The result is that like-level items are sorted together. + The level number provides sameness for levels that are named differently + but are the same level for our purposes. The sort key retains the original + provided level so that like named items still sort together. """ if level_name is None: if (error): @@ -111,8 +129,8 @@ def _lvl_key(level_name, error): for key, val_list in _LEVEL_KEYS.items(): if level_name in val_list: - return '{}{}'.format(key, level_name) - return _SENTINEL_LEVEL + return key, '{}{}'.format(key, level_name) + return _SENTINEL_LEVEL, _SENTINEL_SORT_KEY def _format_validation_message(message): diff --git a/shipyard_client/tests/unit/cli/test_format_utils.py b/shipyard_client/tests/unit/cli/test_format_utils.py index 0cbbebc2..42ff529a 100644 --- a/shipyard_client/tests/unit/cli/test_format_utils.py +++ b/shipyard_client/tests/unit/cli/test_format_utils.py @@ -60,7 +60,6 @@ def test_cli_format_error_handler_no_messages(): resp = MagicMock() resp.json = MagicMock(return_value=json.loads(resp_val)) output = format_utils.cli_format_error_handler(resp) - print(output) assert "Error: Unauthenticated" in output assert "Reason: Credentials are not established" in output @@ -208,7 +207,74 @@ Reason: Validation Document: schema/schema/v1 - someyaml Source: format-o-matic - Info: Basic info - Source: Armadadock""" + Source: Armadadock + +#### Errors: 4, Warnings: 0, Infos: 2, Other: 0 ####""" + resp = MagicMock() + resp.json = MagicMock(return_value=json.loads(resp_val)) + output = format_utils.cli_format_status_handler(resp, is_error=True) + assert output == expected + + +def test_cli_format_status_handler_messages_empty(): + """Tests the generic handler for shipyard status response if passed + a response with no message in the detail to ensure the empty case works. + """ + resp_val = """ +{ + "apiVersion": "v1.0", + "status": "Failure", + "metadata": {}, + "message": "Component Validation Failed", + "code": 400, + "details": { + "errorCount": 0, + "messageList": [] + }, + "kind": "Status", + "reason": "Validation" +} +""" + expected = """Error: Component Validation Failed +Reason: Validation + +#### Errors: 0, Warnings: 0, Infos: 0, Other: 0 ####""" + resp = MagicMock() + resp.json = MagicMock(return_value=json.loads(resp_val)) + output = format_utils.cli_format_status_handler(resp, is_error=True) + assert output == expected + + +def test_cli_format_status_handler_messages_invalid_levels(): + """Tests the generic handler for shipyard status response if passed + a response with no message in the detail to ensure the empty case works. + """ + resp_val = """ +{ + "apiVersion": "v1.0", + "status": "Failure", + "metadata": {}, + "message": "Component Validation Failed", + "code": 400, + "details": { + "errorCount": 0, + "messageList": [ + { "message": "It is broken", + "level": "Broken", + "kind": "ValidationMessage" + } + ] + }, + "kind": "Status", + "reason": "Validation" +} +""" + expected = """Error: Component Validation Failed +Reason: Validation +- Broken: None + Message: It is broken + +#### Errors: 0, Warnings: 0, Infos: 0, Other: 1 ####""" resp = MagicMock() resp.json = MagicMock(return_value=json.loads(resp_val)) output = format_utils.cli_format_status_handler(resp, is_error=True) diff --git a/tests/unit/control/test_shipyard_request_context.py b/tests/unit/control/test_shipyard_request_context.py index f54eaa6a..8c11fdef 100644 --- a/tests/unit/control/test_shipyard_request_context.py +++ b/tests/unit/control/test_shipyard_request_context.py @@ -39,7 +39,6 @@ def test_add_role(): def test_add_roles(): '''test add_roles''' ctx = ShipyardRequestContext() - print(ctx.roles) test_roles = ['Waiter', 'Host', 'Chef'] ctx.add_roles(test_roles) assert ['Chef', 'Host', 'Waiter', 'anyone'] == sorted(ctx.roles)