From db5f22d34e0009c67e80f8ffaf21a769dc7e65d7 Mon Sep 17 00:00:00 2001 From: Bryan Strassner Date: Sat, 17 Mar 2018 15:03:55 -0500 Subject: [PATCH] [391107] Sorting of commit configdocs results Sorts Errors > Warnings > Info in results returned. Adds the source of the error if known. Change-Id: I2d449bc4f999909fe84b10686fdb8e3c7d5fcd5f --- shipyard_client/cli/format_utils.py | 103 +++++++++++++----- .../tests/unit/cli/test_format_utils.py | 27 ++++- 2 files changed, 102 insertions(+), 28 deletions(-) diff --git a/shipyard_client/cli/format_utils.py b/shipyard_client/cli/format_utils.py index b9dd4fa7..4e4b0bca 100644 --- a/shipyard_client/cli/format_utils.py +++ b/shipyard_client/cli/format_utils.py @@ -17,6 +17,8 @@ import yaml from prettytable import PrettyTable from prettytable.prettytable import PLAIN_COLUMNS +_INDENT = ' ' * 8 + def cli_format_error_handler(response): """Generic handler for standard Shipyard error responses @@ -54,40 +56,26 @@ def cli_format_status_handler(response, is_error=False): """ formatted = "Error: {}\nReason: {}" if is_error \ else "Status: {}\nReason: {}" - indent = ' ' * 8 try: if response.text: resp_j = response.json() resp = formatted.format(resp_j.get('message', 'Not specified'), resp_j.get('reason', 'Not specified')) if resp_j.get('details'): - for message in resp_j.get('details').get('messageList', []): + mlist = resp_j['details'].get('messageList', []) + for message in sorted(mlist, + key=lambda m: _lvl_key( + m.get('level'), + m.get('error', False))): if message.get('kind') == 'ValidationMessage': - resp = resp + '\n- {}: {}'.format( - message.get('level'), - message.get('name') - ) - resp = resp + '\n{}Message: {}'.format( - indent, - message.get('message') - ) - if message.get('diagnostic'): - resp = resp + '\n{}Diagnostic: {}'.format( - indent, message.get('diagnostic') - ) - for doc in message.get('documents', []): - resp = resp + '\n{}Document: {} - {}'.format( - indent, - doc.get('schema'), - doc.get('name') - ) + resp = resp + _format_validation_message(message) else: - if message.get('error', False): - resp = resp + '\n- Error: {}'.format( - message.get('message')) - else: - resp = resp + '\n- Info: {}'.format( - message.get('message')) + resp = resp + _format_basic_message(message) + if message.get('source'): + resp = resp + "\n{}Source: {}".format( + _INDENT, + message['source'] + ) return resp else: return '' @@ -96,6 +84,69 @@ def cli_format_status_handler(response, is_error=False): response.text) +# Map of levels by severity. Extra values are included in this map but valid +# values are defined here: +# https://github.com/att-comdev/ucp-integration/blob/master/docs/source/api-conventions.rst#validationmessage-message-type +_LEVEL_KEYS = { + 0: ['error', 'fatal'], + 1: ['warn', 'warning'], + 2: ['info', 'debug'], +} +_SENTINEL_LEVEL = "999" + + +def _lvl_key(level_name, error): + """Generate a level key value + + Returns a value to support sort order based on lvls dict. + The result is that like-level items are sorted together. + """ + if level_name is None: + if (error): + level_name = 'error' + else: + level_name = 'info' + else: + level_name = level_name.lower() + + for key, val_list in _LEVEL_KEYS.items(): + if level_name in val_list: + return '{}{}'.format(key, level_name) + return _SENTINEL_LEVEL + + +def _format_validation_message(message): + """Formats a ValidationMessage + + Returns a single string with embedded newlines + """ + resp = '\n- {}: {}'.format(message.get('level'), message.get('name')) + resp = resp + '\n{}Message: {}'.format(_INDENT, message.get('message')) + if message.get('diagnostic'): + resp = resp + '\n{}Diagnostic: {}'.format( + _INDENT, message.get('diagnostic') + ) + for doc in message.get('documents', []): + resp = resp + '\n{}Document: {} - {}'.format( + _INDENT, + doc.get('schema'), + doc.get('name') + ) + return resp + + +def _format_basic_message(message): + """Formats a basic message + + Returns a single string with embedded newlines + """ + if message.get('error', False): + resp = '\n- Error: {}'.format(message.get('message')) + else: + resp = '\n- Info: {}'.format(message.get('message')) + return resp + + def raw_format_response_handler(response): """Basic format handler to return raw response text""" return response.text diff --git a/shipyard_client/tests/unit/cli/test_format_utils.py b/shipyard_client/tests/unit/cli/test_format_utils.py index 80955d38..0cbbebc2 100644 --- a/shipyard_client/tests/unit/cli/test_format_utils.py +++ b/shipyard_client/tests/unit/cli/test_format_utils.py @@ -130,7 +130,8 @@ def test_cli_format_error_handler_messages_broken(): def test_cli_format_status_handler_messages(): """Tests the generic handler for shipyard status response if passed - a response with messages in the detail + a response with messages in the detail. Includes sorting and source + information. """ resp_val = """ { @@ -142,6 +143,18 @@ def test_cli_format_status_handler_messages(): "details": { "errorCount": 4, "messageList": [ + { "message":"Info something you might want to know", + "error": false, + "kind": "ValidationMessage", + "name": "val0", + "documents": [ + { "schema": "schema/schema/v1", + "name": "someyaml" + } + ], + "level": "Info", + "source": "format-o-matic" + }, { "message":"Conflicting something", "error": true, "kind": "ValidationMessage", @@ -154,6 +167,10 @@ def test_cli_format_status_handler_messages(): "level": "Error", "diagnostic": "Make a doc change" }, + { "message": "Basic info", + "error": false, + "source": "Armadadock" + }, { "message":"Backwards something", "error": true, "kind": "ValidationMessage", @@ -185,7 +202,13 @@ Reason: Validation - Error: val2 Message: Backwards something - Error: Missing stuff -- Error: Broken syntax""" +- Error: Broken syntax +- Info: val0 + Message: Info something you might want to know + Document: schema/schema/v1 - someyaml + Source: format-o-matic +- Info: Basic info + Source: Armadadock""" resp = MagicMock() resp.json = MagicMock(return_value=json.loads(resp_val)) output = format_utils.cli_format_status_handler(resp, is_error=True)