diff --git a/.coveragerc b/.coveragerc index 0de7328a4..1736e29e4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,6 +3,7 @@ plugins = covdefaults omit = splunk_add_on_ucc_framework/commands/imports.py splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/alert_action_helper.py.template + splunk_add_on_ucc_framework/templates/input.module-template [report] -fail_under = 77 +fail_under = 78.5 diff --git a/splunk_add_on_ucc_framework/commands/build.py b/splunk_add_on_ucc_framework/commands/build.py index 671c82f98..ce2c92dbd 100644 --- a/splunk_add_on_ucc_framework/commands/build.py +++ b/splunk_add_on_ucc_framework/commands/build.py @@ -249,6 +249,7 @@ def generate_data_ui( addon_name: str, include_inputs: bool, include_dashboard: bool, + default_view: str, ) -> None: # Create directories in the output folder for add-on's UI nav and views. os.makedirs( @@ -272,6 +273,7 @@ def generate_data_ui( default_xml_content = data_ui_generator.generate_nav_default_xml( include_inputs=include_inputs, include_dashboard=include_dashboard, + default_view=default_view, ) default_xml_file.write(default_xml_content) with open( @@ -556,6 +558,7 @@ def generate( ta_name, global_config.has_inputs(), global_config.has_dashboard(), + global_config.meta.get("default_view", data_ui_generator.DEFAULT_VIEW), ) logger.info("Copied UCC template directory") global_config_file = ( diff --git a/splunk_add_on_ucc_framework/data_ui_generator.py b/splunk_add_on_ucc_framework/data_ui_generator.py index d9c004360..b397aee48 100644 --- a/splunk_add_on_ucc_framework/data_ui_generator.py +++ b/splunk_add_on_ucc_framework/data_ui_generator.py @@ -20,6 +20,8 @@ from xml.etree import ElementTree as ET from defusedxml import minidom +DEFAULT_VIEW = "configuration" + def _pretty_print_xml(string: str) -> str: """ @@ -28,17 +30,33 @@ def _pretty_print_xml(string: str) -> str: return minidom.parseString(string).toprettyxml(indent=" ") -def generate_nav_default_xml(include_inputs: bool, include_dashboard: bool) -> str: +def generate_nav_default_xml( + include_inputs: bool, include_dashboard: bool, default_view: str +) -> str: """ Generates `default/data/ui/nav/default.xml` file. + + The validation is being done in `_validate_meta_default_view` function from `global_config_validator.py` file. """ nav = ET.Element("nav") if include_inputs: - ET.SubElement(nav, "view", attrib={"name": "inputs"}) - ET.SubElement(nav, "view", attrib={"name": "configuration", "default": "true"}) + if default_view == "inputs": + ET.SubElement(nav, "view", attrib={"name": "inputs", "default": "true"}) + else: + ET.SubElement(nav, "view", attrib={"name": "inputs"}) + if default_view == "configuration": + ET.SubElement(nav, "view", attrib={"name": "configuration", "default": "true"}) + else: + ET.SubElement(nav, "view", attrib={"name": "configuration"}) if include_dashboard: - ET.SubElement(nav, "view", attrib={"name": "dashboard"}) - ET.SubElement(nav, "view", attrib={"name": "search"}) + if default_view == "dashboard": + ET.SubElement(nav, "view", attrib={"name": "dashboard", "default": "true"}) + else: + ET.SubElement(nav, "view", attrib={"name": "dashboard"}) + if default_view == "search": + ET.SubElement(nav, "view", attrib={"name": "search", "default": "true"}) + else: + ET.SubElement(nav, "view", attrib={"name": "search"}) nav_as_string = ET.tostring(nav, encoding="unicode") return _pretty_print_xml(nav_as_string) diff --git a/splunk_add_on_ucc_framework/global_config.py b/splunk_add_on_ucc_framework/global_config.py index ac88efe55..0962bc571 100644 --- a/splunk_add_on_ucc_framework/global_config.py +++ b/splunk_add_on_ucc_framework/global_config.py @@ -136,7 +136,7 @@ def alerts(self) -> List[Dict[str, Any]]: return self._content.get("alerts", []) @property - def meta(self) -> Dict[str, str]: + def meta(self) -> Dict[str, Any]: return self._content["meta"] @property diff --git a/splunk_add_on_ucc_framework/global_config_validator.py b/splunk_add_on_ucc_framework/global_config_validator.py index d64b2cbf1..875f0c5db 100644 --- a/splunk_add_on_ucc_framework/global_config_validator.py +++ b/splunk_add_on_ucc_framework/global_config_validator.py @@ -21,11 +21,11 @@ import logging import itertools - import jsonschema from splunk_add_on_ucc_framework import dashboard as dashboard_lib from splunk_add_on_ucc_framework import global_config as global_config_lib +from splunk_add_on_ucc_framework import data_ui_generator from splunk_add_on_ucc_framework.tabs import resolve_tab, Tab logger = logging.getLogger("ucc_gen") @@ -43,6 +43,7 @@ class GlobalConfigValidator: def __init__(self, source_dir: str, global_config: global_config_lib.GlobalConfig): self._source_dir = source_dir + self._global_config = global_config self._config = global_config.content @property @@ -613,11 +614,11 @@ def _is_circular( visited = self._is_circular( mods, visited, all_entity_fields, influenced_field ) - # all of dependent modifications fields are dead_end + # All dependent modifications fields are dead_end visited[current_field] = DEAD_END return visited - def _check_if_cilcular( + def _check_if_circular( self, all_entity_fields: List[Any], fields_with_mods: List[Any], @@ -672,7 +673,7 @@ def _get_all_entities( return all_fields - def _get_all_modifiction_data( + def _get_all_modification_data( self, collections: List[Dict[str, Any]], ) -> List[Any]: @@ -692,9 +693,9 @@ def _get_all_modifiction_data( def _validate_field_modifications(self) -> None: """ Validates: - Circular dependencies - If fields try modify itself - If fields try modify unexisting field + * Circular dependencies + * If fields try to modify itself + * If fields try to modify field that do not exist """ pages = self._config["pages"] @@ -706,9 +707,9 @@ def _validate_field_modifications(self) -> None: fields_with_mods_config, all_modifications_config, all_fields_config, - ) = self._get_all_modifiction_data(tabs) + ) = self._get_all_modification_data(tabs) - self._check_if_cilcular( + self._check_if_circular( all_fields_config, fields_with_mods_config, all_modifications_config ) @@ -720,12 +721,25 @@ def _validate_field_modifications(self) -> None: fields_with_mods_inputs, all_modifications_inputs, all_fields_inputs, - ) = self._get_all_modifiction_data(services) + ) = self._get_all_modification_data(services) - self._check_if_cilcular( + self._check_if_circular( all_fields_inputs, fields_with_mods_inputs, all_modifications_inputs ) + def _validate_meta_default_view(self) -> None: + default_view = self._global_config.meta.get( + "defaultView", data_ui_generator.DEFAULT_VIEW + ) + if default_view == "inputs" and not self._global_config.has_inputs(): + raise GlobalConfigValidatorException( + 'meta.defaultView == "inputs" but there is no inputs defined in globalConfig' + ) + if default_view == "dashboard" and not self._global_config.has_dashboard(): + raise GlobalConfigValidatorException( + 'meta.defaultView == "dashboard" but there is no dashboard defined in globalConfig' + ) + def validate(self) -> None: self._validate_config_against_schema() self._validate_configuration_tab_table_has_name_field() @@ -740,3 +754,4 @@ def validate(self) -> None: self._validate_checkbox_group() self._validate_groups() self._validate_field_modifications() + self._validate_meta_default_view() diff --git a/splunk_add_on_ucc_framework/schema/schema.json b/splunk_add_on_ucc_framework/schema/schema.json index af1dd9600..147539a84 100644 --- a/splunk_add_on_ucc_framework/schema/schema.json +++ b/splunk_add_on_ucc_framework/schema/schema.json @@ -1690,6 +1690,23 @@ "type": "boolean", "default": true }, + "defaultView": { + "type": "string", + "anyOf": [ + { + "const": "inputs" + }, + { + "const": "configuration" + }, + { + "const": "dashboard" + }, + { + "const": "search" + } + ] + }, "os-dependentLibraries": { "type": "array", "description": "This feature allows you to download and unpack libraries with appropriate binaries for the indicated operating system during the build process.", @@ -1753,7 +1770,7 @@ } }, "required": ["displayName", "name", "restRoot", "version"], - "description": "Meta deta regarding build", + "description": "Metadata regarding build", "additionalProperties": false }, "NumberValidator": { diff --git a/tests/testdata/test_addons/package_files_conflict_test/globalConfig.json b/tests/testdata/test_addons/package_files_conflict_test/globalConfig.json index 33935d372..cbe6df45a 100644 --- a/tests/testdata/test_addons/package_files_conflict_test/globalConfig.json +++ b/tests/testdata/test_addons/package_files_conflict_test/globalConfig.json @@ -81,7 +81,8 @@ ] }, "defaultValue": "INFO", - "field": "loglevel" + "field": "loglevel", + "required": true } ], "title": "Logging" @@ -116,19 +117,12 @@ "required": true }, { - "type": "text", - "label": "Interval", - "validators": [ - { - "type": "regex", - "errorMsg": "Interval must be an integer.", - "pattern": "^\\-[1-9]\\d*$|^\\d*$" - } - ], - "defaultValue": "300", + "type": "interval", "field": "interval", + "label": "Interval", "help": "Time interval of the data input, in seconds.", - "required": true + "required": true, + "defaultValue": "300" }, { "type": "singleSelect", @@ -206,9 +200,9 @@ "meta": { "name": "test_addon", "restRoot": "test_addon", - "version": "5.41.0Reda406cf", + "version": "5.44.0R7f88cfdd", "displayName": "This is my add-on", - "schemaVersion": "0.0.6", - "_uccVersion": "5.42.1" + "schemaVersion": "0.0.7", + "_uccVersion": "5.44.0" } } diff --git a/tests/testdata/test_addons/package_global_config_configuration/globalConfig.json b/tests/testdata/test_addons/package_global_config_configuration/globalConfig.json index 66c318dc8..69fc1e228 100644 --- a/tests/testdata/test_addons/package_global_config_configuration/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_configuration/globalConfig.json @@ -386,7 +386,7 @@ "restRoot": "splunk_ta_uccexample", "version": "1.1.1", "displayName": "Splunk UCC test Add-on", - "schemaVersion": "0.0.6", - "_uccVersion": "5.42.1" + "schemaVersion": "0.0.7", + "_uccVersion": "5.44.0" } } diff --git a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json index e10b794f1..01cd0271b 100644 --- a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json @@ -1550,7 +1550,7 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.44.0Re9cd7340", + "version": "5.44.0R7f88cfdd", "displayName": "Splunk UCC test Add-on", "schemaVersion": "0.0.7", "_uccVersion": "5.44.0" diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json b/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json index cc30cb20b..b19fc1c35 100644 --- a/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/globalConfig.json @@ -602,16 +602,9 @@ } }, { - "type": "text", - "label": "Interval", - "validators": [ - { - "type": "regex", - "errorMsg": "Interval must be an integer.", - "pattern": "^\\-[1-9]\\d*$|^\\d*$" - } - ], + "type": "interval", "field": "interval", + "label": "Interval", "help": "Time interval of the data input, in seconds.", "required": true }, @@ -791,16 +784,9 @@ "required": true }, { - "type": "text", - "label": "Interval", - "validators": [ - { - "type": "regex", - "errorMsg": "Interval must be an integer.", - "pattern": "^\\-[1-9]\\d*$|^\\d*$" - } - ], + "type": "interval", "field": "interval", + "label": "Interval", "help": "Time interval of the data input, in seconds .", "required": true }, @@ -1186,9 +1172,9 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.42.1R67372a0b", + "version": "5.44.0R7f88cfdd", "displayName": "Splunk UCC test Add-on", - "schemaVersion": "0.0.6", - "_uccVersion": "5.42.1" + "schemaVersion": "0.0.7", + "_uccVersion": "5.44.0" } } diff --git a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json index af6f8feae..9b08d61a9 100644 --- a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json @@ -431,7 +431,7 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.44.0Re9cd7340", + "version": "5.44.0R7f88cfdd", "displayName": "Splunk UCC test Add-on", "schemaVersion": "0.0.7", "_uccVersion": "5.44.0" diff --git a/tests/testdata/test_addons/package_global_config_only_one_tab/globalConfig.json b/tests/testdata/test_addons/package_global_config_only_one_tab/globalConfig.json index 1ffe3345b..cd2e7abb3 100644 --- a/tests/testdata/test_addons/package_global_config_only_one_tab/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_only_one_tab/globalConfig.json @@ -36,9 +36,9 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.41.0Reda406cf", + "version": "5.44.0R7f88cfdd", "displayName": "Splunk UCC test Add-on", - "schemaVersion": "0.0.6", - "_uccVersion": "5.42.1" + "schemaVersion": "0.0.7", + "_uccVersion": "5.44.0" } } diff --git a/tests/unit/test_data_ui_generator.py b/tests/unit/test_data_ui_generator.py index 0412d8405..0ca2c14a6 100644 --- a/tests/unit/test_data_ui_generator.py +++ b/tests/unit/test_data_ui_generator.py @@ -1,21 +1,13 @@ -import sys - -import pytest +import xmldiff.main from splunk_add_on_ucc_framework import data_ui_generator -PYTEST_SKIP_REASON = """Python 3.8 and higher preserves the order of the attrib -fields when `tostring` function is used. -https://docs.python.org/3/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring -""" - - -@pytest.mark.skipif(sys.version_info >= (3, 8), reason=PYTEST_SKIP_REASON) def test_generate_nav_default_xml(): result = data_ui_generator.generate_nav_default_xml( include_inputs=True, include_dashboard=True, + default_view="configuration", ) expected_result = """ @@ -26,14 +18,16 @@ def test_generate_nav_default_xml(): """ - assert expected_result == result + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" -@pytest.mark.skipif(sys.version_info >= (3, 8), reason=PYTEST_SKIP_REASON) def test_generate_nav_default_xml_only_configuration(): result = data_ui_generator.generate_nav_default_xml( include_inputs=False, include_dashboard=False, + default_view="configuration", ) expected_result = """ @@ -42,10 +36,68 @@ def test_generate_nav_default_xml_only_configuration(): """ - assert expected_result == result + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" + + +def test_generate_nav_default_xml_with_default_inputs_page(): + result = data_ui_generator.generate_nav_default_xml( + include_inputs=True, + include_dashboard=False, + default_view="inputs", + ) + + expected_result = """ + +""" + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" + + +def test_generate_nav_default_xml_with_default_dashboard_page(): + result = data_ui_generator.generate_nav_default_xml( + include_inputs=True, + include_dashboard=True, + default_view="dashboard", + ) + + expected_result = """ + +""" + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" + + +def test_generate_nav_default_xml_with_search_view_default(): + result = data_ui_generator.generate_nav_default_xml( + include_inputs=False, + include_dashboard=False, + default_view="search", + ) + + expected_result = """ + +""" + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" -@pytest.mark.skipif(sys.version_info >= (3, 8), reason=PYTEST_SKIP_REASON) def test_generate_views_inputs_xml(): result = data_ui_generator.generate_views_inputs_xml("Splunk_TA_UCCExample") @@ -54,10 +106,11 @@ def test_generate_views_inputs_xml(): """ - assert expected_result == result + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" -@pytest.mark.skipif(sys.version_info >= (3, 8), reason=PYTEST_SKIP_REASON) def test_generate_views_configuration_xml(): result = data_ui_generator.generate_views_configuration_xml("Splunk_TA_UCCExample") @@ -66,10 +119,24 @@ def test_generate_views_configuration_xml(): """ - assert expected_result == result + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" + + +def test_generate_views_dashboard_xml(): + result = data_ui_generator.generate_views_dashboard_xml("Splunk_TA_UCCExample") + + expected_result = """ + + + +""" + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" -@pytest.mark.skipif(sys.version_info >= (3, 8), reason=PYTEST_SKIP_REASON) def test_generate_views_redirect_xml(): result = data_ui_generator.generate_views_redirect_xml("Splunk_TA_UCCExample") @@ -78,4 +145,6 @@ def test_generate_views_redirect_xml(): """ - assert expected_result == result + diff = xmldiff.main.diff_texts(result, expected_result) + + assert " ".join([str(item) for item in diff]) == "" diff --git a/tests/unit/test_global_config_validator.py b/tests/unit/test_global_config_validator.py index 836481fab..9e1080653 100644 --- a/tests/unit/test_global_config_validator.py +++ b/tests/unit/test_global_config_validator.py @@ -304,6 +304,20 @@ def test_config_validation_when_deprecated_placeholder_is_used(caplog): "undefined_entity_field_name which is not defined in entity" ), ), + ( + "invalid_config_meta_default_inputs_page_but_no_inputs_defined.json", + False, + ( + 'meta.defaultView == "inputs" but there is no inputs defined in globalConfig' + ), + ), + ( + "invalid_config_meta_default_dashboard_page_but_no_dashboard_defined.json", + False, + ( + 'meta.defaultView == "dashboard" but there is no dashboard defined in globalConfig' + ), + ), ], ) def test_config_validation_when_error(filename, exception_message): diff --git a/tests/unit/testdata/invalid_config_meta_default_dashboard_page_but_no_dashboard_defined.json b/tests/unit/testdata/invalid_config_meta_default_dashboard_page_but_no_dashboard_defined.json new file mode 100644 index 000000000..f82410494 --- /dev/null +++ b/tests/unit/testdata/invalid_config_meta_default_dashboard_page_but_no_dashboard_defined.json @@ -0,0 +1,55 @@ +{ + "pages": { + "configuration": { + "tabs": [ + { + "name": "logging", + "entity": [ + { + "type": "singleSelect", + "label": "Log level", + "options": { + "disableSearch": true, + "autoCompleteFields": [ + { + "value": "DEBUG", + "label": "DEBUG" + }, + { + "value": "INFO", + "label": "INFO" + }, + { + "value": "WARNING", + "label": "WARNING" + }, + { + "value": "ERROR", + "label": "ERROR" + }, + { + "value": "CRITICAL", + "label": "CRITICAL" + } + ] + }, + "defaultValue": "INFO", + "field": "loglevel" + } + ], + "title": "Logging" + } + ], + "title": "Configuration", + "description": "Set up your add-on" + } + }, + "meta": { + "name": "Splunk_TA_UCCExample", + "restRoot": "splunk_ta_uccexample", + "version": "1.0.0", + "displayName": "Splunk UCC test Add-on", + "schemaVersion": "0.0.3", + "defaultView": "dashboard" + } +} diff --git a/tests/unit/testdata/invalid_config_meta_default_inputs_page_but_no_inputs_defined.json b/tests/unit/testdata/invalid_config_meta_default_inputs_page_but_no_inputs_defined.json new file mode 100644 index 000000000..e09e97723 --- /dev/null +++ b/tests/unit/testdata/invalid_config_meta_default_inputs_page_but_no_inputs_defined.json @@ -0,0 +1,55 @@ +{ + "pages": { + "configuration": { + "tabs": [ + { + "name": "logging", + "entity": [ + { + "type": "singleSelect", + "label": "Log level", + "options": { + "disableSearch": true, + "autoCompleteFields": [ + { + "value": "DEBUG", + "label": "DEBUG" + }, + { + "value": "INFO", + "label": "INFO" + }, + { + "value": "WARNING", + "label": "WARNING" + }, + { + "value": "ERROR", + "label": "ERROR" + }, + { + "value": "CRITICAL", + "label": "CRITICAL" + } + ] + }, + "defaultValue": "INFO", + "field": "loglevel" + } + ], + "title": "Logging" + } + ], + "title": "Configuration", + "description": "Set up your add-on" + } + }, + "meta": { + "name": "Splunk_TA_UCCExample", + "restRoot": "splunk_ta_uccexample", + "version": "1.0.0", + "displayName": "Splunk UCC test Add-on", + "schemaVersion": "0.0.3", + "defaultView": "inputs" + } +} diff --git a/ui/src/components/BaseFormView/BaseFormConfigMock.ts b/ui/src/components/BaseFormView/BaseFormConfigMock.ts index b38b08770..fe14d6463 100644 --- a/ui/src/components/BaseFormView/BaseFormConfigMock.ts +++ b/ui/src/components/BaseFormView/BaseFormConfigMock.ts @@ -167,6 +167,7 @@ const globalConfigMockCustomControl = { displayName: 'Demo Add-on for Splunk', schemaVersion: '0.0.3', checkForUpdates: false, + searchViewDefault: false, }, } satisfies z.input; diff --git a/ui/src/mocks/globalConfigMock.ts b/ui/src/mocks/globalConfigMock.ts index bd7738394..64a528815 100644 --- a/ui/src/mocks/globalConfigMock.ts +++ b/ui/src/mocks/globalConfigMock.ts @@ -248,6 +248,7 @@ const globalConfigMock = { displayName: 'Demo Add-on for Splunk', schemaVersion: '0.0.3', checkForUpdates: false, + searchViewDefault: false, }, } satisfies z.input; diff --git a/ui/src/types/globalConfig/meta.ts b/ui/src/types/globalConfig/meta.ts index d0a1d19b6..26d9665ad 100644 --- a/ui/src/types/globalConfig/meta.ts +++ b/ui/src/types/globalConfig/meta.ts @@ -8,6 +8,7 @@ export const meta = z.object({ version: z.string(), schemaVersion: z.string().optional(), checkForUpdates: z.boolean().default(true).optional(), + searchViewDefault: z.boolean().default(false).optional(), }); export type meta = z.infer;