Skip to content

Commit

Permalink
feat: make configuration page as optional (#1521)
Browse files Browse the repository at this point in the history
**Issue number:**
ADDON-76819

### PR Type

**What kind of change does this PR introduce?**
* [x] Feature
* [ ] Bug Fix
* [ ] Refactoring (no functional or API changes)
* [ ] Documentation Update
* [ ] Maintenance (dependency updates, CI, etc.)

## Summary
Set configuration as an optional key in `globalConfig.json`.

### Changes
Made configuration page optional in `schema.json`, So that users can
have an add-on UI without Configuration page.
Also added a smoke testcase which captures this behaviour.

### User experience
Now, user can build their add-on without specifying `configuration` in
their `globalConfig.json`. Users now can have a UI without Configuration
page for their add-on.

## Checklist

If an item doesn't apply to your changes, leave it unchecked.

* [x] I have performed a self-review of this change according to the
[development
guidelines](https://splunk.github.io/addonfactory-ucc-generator/contributing/#development-guidelines)
* [x] Tests have been added/modified to cover the changes [(testing
doc)](https://splunk.github.io/addonfactory-ucc-generator/contributing/#build-and-test)
* [ ] Changes are documented
* [x] PR title and description follows the [contributing
principles](https://splunk.github.io/addonfactory-ucc-generator/contributing/#pull-requests)

---------

Signed-off-by: Viktor Tsvetkov <142901247+vtsvetkov-splunk@users.noreply.github.com>
Co-authored-by: rohanm-crest <rohanm@splunk.com>
Co-authored-by: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com>
Co-authored-by: Viktor Tsvetkov <142901247+vtsvetkov-splunk@users.noreply.github.com>
  • Loading branch information
4 people authored Feb 11, 2025
1 parent d86342a commit 0f67e30
Show file tree
Hide file tree
Showing 64 changed files with 3,469 additions and 79 deletions.
10 changes: 5 additions & 5 deletions docs/generated_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ The following table describes the files generated by UCC framework.
| tags.conf | output/&lt;YOUR_ADDON_NAME&gt;/default | Generates `tags.conf` file based on the `eventtypes.conf` created for custom alert actions. |
| _account.conf | output/&lt;YOUR_ADDON_NAME&gt;/README | Generates `<YOUR_ADDON_NAME>_account.conf.spec` file for the configuration mentioned in globalConfig |
| _settings.conf | output/&lt;YOUR_ADDON_NAME&gt;/README | Generates `<YOUR_ADDON_NAME>_settings.conf.spec` file for the Proxy, Logging or Custom Tab mentioned in globalConfig |
| configuration.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates configuration.xml file in `default/data/ui/views/` folder if globalConfig is present. |
| dashboard.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates dashboard.xml file based on dashboard configuration present in globalConfig in `default/data/ui/views` folder. |
| default.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/nav | Generates default.xml file based on configs present in globalConfigin in `default/data/ui/nav` folder. |
| configuration.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates configuration.xml file in `default/data/ui/views/` folder if configuration is defined in globalConfig. |
| dashboard.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates dashboard.xml file based on dashboard configuration present in globalConfig, in `default/data/ui/views` folder. |
| default.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/nav | Generates default.xml file based on configs present in globalConfig, in `default/data/ui/nav` folder. |
| inputs.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates inputs.xml based on inputs configuration present in globalConfig, in `default/data/ui/views/inputs.xml` folder |
| _redirect.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates ta_name_redirect.xml file, if oauth is mentioned in globalConfig,in `default/data/ui/views/` folder. |
| _.html | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/alerts | Generates `alert_name.html` file based on alerts configuration present in globalConfig in `default/data/ui/alerts` folder. |
| _redirect.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates ta_name_redirect.xml file, if oauth is mentioned in globalConfig, in `default/data/ui/views/` folder. |
| _.html | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/alerts | Generates `alert_name.html` file based on alerts configuration present in globalConfig, in `default/data/ui/alerts` folder. |

Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,17 @@ def __add_schemas_object(
) -> OpenAPIObject:
if open_api_object.components is not None:
open_api_object.components.schemas = {}
for tab in global_config.pages.configuration.tabs: # type: ignore[attr-defined]
schema_name, schema_object = __get_schema_object(
name=tab.name, entities=tab.entity
)
open_api_object.components.schemas[schema_name] = schema_object
schema_name, schema_object = __get_schema_object(
name=tab.name, entities=tab.entity, without=["name"]
)
open_api_object.components.schemas[schema_name] = schema_object
pages = getattr(global_config, "pages", None)
if hasattr(pages, "configuration"):
for tab in global_config.pages.configuration.tabs: # type: ignore[attr-defined]
schema_name, schema_object = __get_schema_object(
name=tab.name, entities=tab.entity
)
open_api_object.components.schemas[schema_name] = schema_object
schema_name, schema_object = __get_schema_object(
name=tab.name, entities=tab.entity, without=["name"]
)
open_api_object.components.schemas[schema_name] = schema_object
if hasattr(global_config.pages, "inputs") and hasattr( # type: ignore[attr-defined]
global_config.pages.inputs, "services" # type: ignore[attr-defined]
):
Expand Down Expand Up @@ -378,18 +380,20 @@ def __assign_ta_paths(
def __add_paths(
open_api_object: OpenAPIObject, global_config: DataClasses
) -> OpenAPIObject:
for tab in global_config.pages.configuration.tabs: # type: ignore[attr-defined]
open_api_object = __assign_ta_paths(
open_api_object=open_api_object,
path=f"/{global_config.meta.restRoot}_{tab.name}" # type: ignore[attr-defined]
if hasattr(tab, "table")
else f"/{global_config.meta.restRoot}_settings/{tab.name}", # type: ignore[attr-defined]
path_name=tab.name,
actions=tab.table.actions
if hasattr(tab, "table") and hasattr(tab.table, "actions")
else None,
page=GloblaConfigPages.CONFIGURATION,
)
pages = getattr(global_config, "pages", None)
if hasattr(pages, "configuration"):
for tab in global_config.pages.configuration.tabs: # type: ignore[attr-defined]
open_api_object = __assign_ta_paths(
open_api_object=open_api_object,
path=f"/{global_config.meta.restRoot}_{tab.name}" # type: ignore[attr-defined]
if hasattr(tab, "table")
else f"/{global_config.meta.restRoot}_settings/{tab.name}", # type: ignore[attr-defined]
path_name=tab.name,
actions=tab.table.actions
if hasattr(tab, "table") and hasattr(tab.table, "actions")
else None,
page=GloblaConfigPages.CONFIGURATION,
)
if hasattr(global_config.pages, "inputs") and hasattr( # type: ignore[attr-defined]
global_config.pages.inputs, "services" # type: ignore[attr-defined]
):
Expand Down
32 changes: 25 additions & 7 deletions splunk_add_on_ucc_framework/data_ui_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
# nosemgrep: splunk.use-defused-xml
from xml.etree import ElementTree as ET
from defusedxml import minidom

DEFAULT_VIEW = "configuration"
from typing import Optional


def _pretty_print_xml(string: str) -> str:
Expand All @@ -31,23 +30,41 @@ def _pretty_print_xml(string: str) -> str:


def generate_nav_default_xml(
include_inputs: bool, include_dashboard: bool, default_view: str
include_inputs: bool,
include_dashboard: bool,
include_configuration: bool,
default_view: Optional[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 default_view is None:
# we do this calculation as all the below properties are now optional
if include_configuration:
default_view = "configuration"
elif include_inputs:
default_view = "inputs"
elif include_dashboard:
default_view = "dashboard"
else:
default_view = "search"

if include_inputs:
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_configuration:
if default_view == "configuration":
ET.SubElement(
nav, "view", attrib={"name": "configuration", "default": "true"}
)
else:
ET.SubElement(nav, "view", attrib={"name": "configuration"})
if include_dashboard:
if default_view == "dashboard":
ET.SubElement(nav, "view", attrib={"name": "dashboard", "default": "true"})
Expand All @@ -57,6 +74,7 @@ def generate_nav_default_xml(
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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

class AlertActionsHtml(HTMLGenerator):
__description__ = (
"Generates `alert_name.html` file based on alerts configuration present in globalConfig"
"Generates `alert_name.html` file based on alerts configuration present in globalConfig,"
" in `default/data/ui/alerts` folder."
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,27 @@
# limitations under the License.
#
from splunk_add_on_ucc_framework.generators.xml_files import XMLGenerator
from typing import Any, Dict
from typing import Any, Dict, Union
from splunk_add_on_ucc_framework import data_ui_generator


class ConfigurationXml(XMLGenerator):
__description__ = "Generates configuration.xml file in `default/data/ui/views/` folder if globalConfig is present."
__description__ = (
"Generates configuration.xml file in `default/data/ui/views/` folder if "
"configuration is defined in globalConfig."
)

def _set_attributes(self, **kwargs: Any) -> None:
self.configuration_xml_content = (
data_ui_generator.generate_views_configuration_xml(
self._addon_name,
if self._global_config and self._global_config.has_configuration():
self.configuration_xml_content = (
data_ui_generator.generate_views_configuration_xml(
self._addon_name,
)
)
)

def generate_xml(self) -> Dict[str, str]:
def generate_xml(self) -> Union[Dict[str, str], None]:
if self._global_config and not self._global_config.has_configuration():
return None
file_path = self.get_file_output_path(
["default", "data", "ui", "views", "configuration.xml"]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

class DashboardXml(XMLGenerator):
__description__ = (
"Generates dashboard.xml file based on dashboard configuration present in globalConfig"
"Generates dashboard.xml file based on dashboard configuration present in globalConfig,"
" in `default/data/ui/views` folder."
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

class DefaultXml(XMLGenerator):
__description__ = (
"Generates default.xml file based on configs present in globalConfig"
"in in `default/data/ui/nav` folder."
"Generates default.xml file based on configs present in globalConfig,"
" in `default/data/ui/nav` folder."
)

def _set_attributes(self, **kwargs: Any) -> None:
Expand All @@ -45,9 +45,8 @@ def _set_attributes(self, **kwargs: Any) -> None:
self.default_xml_content = data_ui_generator.generate_nav_default_xml(
include_inputs=self._global_config.has_inputs(),
include_dashboard=self._global_config.has_dashboard(),
default_view=self._global_config.meta.get(
"default_view", data_ui_generator.DEFAULT_VIEW
),
include_configuration=self._global_config.has_configuration(),
default_view=self._global_config.meta.get("default_view"),
)

def generate_xml(self) -> Dict[str, str]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
class RedirectXml(XMLGenerator):
__description__ = (
"Generates ta_name_redirect.xml file, if oauth is mentioned in globalConfig,"
"in `default/data/ui/views/` folder."
" in `default/data/ui/views/` folder."
)

def _set_attributes(self, **kwargs: Any) -> None:
Expand Down
16 changes: 12 additions & 4 deletions splunk_add_on_ucc_framework/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,14 @@ def expand(self) -> None:
self.expand_entities()

def expand_tabs(self) -> None:
for i, tab in enumerate(self._content["pages"]["configuration"]["tabs"]):
self._content["pages"]["configuration"]["tabs"][i] = resolve_tab(tab)
if self.has_configuration():
for i, tab in enumerate(self._content["pages"]["configuration"]["tabs"]):
self._content["pages"]["configuration"]["tabs"][i] = resolve_tab(tab)

def expand_entities(self) -> None:
self._expand_entities(self._content["pages"]["configuration"]["tabs"])
self._expand_entities(
self._content["pages"].get("configuration", {}).get("tabs")
)
self._expand_entities(self._content["pages"].get("inputs", {}).get("services"))
self._expand_entities(self._content.get("alerts"))

Expand All @@ -117,7 +120,9 @@ def inputs(self) -> List[Any]:

@property
def tabs(self) -> List[Any]:
return self._content["pages"]["configuration"]["tabs"]
if "configuration" in self._content["pages"]:
return self._content["pages"]["configuration"]["tabs"]
return []

@property
def dashboard(self) -> Dict[str, Any]:
Expand Down Expand Up @@ -207,6 +212,9 @@ def add_ucc_version(self, version: str) -> None:
def has_inputs(self) -> bool:
return bool(self.inputs)

def has_configuration(self) -> bool:
return bool(self.tabs)

def has_alerts(self) -> bool:
return bool(self.alerts)

Expand Down
2 changes: 1 addition & 1 deletion splunk_add_on_ucc_framework/global_config_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def _dump_with_migrated_entities(
global_config.content["pages"].get("inputs", {}).get("services"), entity_type
)
_collapse_entities(
global_config.content["pages"]["configuration"].get("tabs"), entity_type
global_config.content["pages"].get("configuration", {}).get("tabs"), entity_type
)
_collapse_entities(global_config.content.get("alerts"), entity_type)

Expand Down
26 changes: 16 additions & 10 deletions splunk_add_on_ucc_framework/global_config_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

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
from splunk_add_on_ucc_framework.exceptions import GlobalConfigValidatorException

Expand Down Expand Up @@ -54,10 +53,13 @@ def __init__(self, source_dir: str, global_config: global_config_lib.GlobalConfi
self._config = global_config.content

@property
def config_tabs(self) -> List[Tab]:
return [
resolve_tab(tab) for tab in self._config["pages"]["configuration"]["tabs"]
]
def config_tabs(self) -> List[Any]:
if self._global_config.has_configuration():
return [
resolve_tab(tab)
for tab in self._config["pages"]["configuration"]["tabs"]
]
return []

def _validate_config_against_schema(self) -> None:
"""
Expand Down Expand Up @@ -430,7 +432,6 @@ def _validate_duplicates(self) -> None:
not required in schema, so this checks if globalConfig has inputs
"""
pages = self._config["pages"]

self._validate_tabs_duplicates(self.config_tabs)

inputs = pages.get("inputs")
Expand Down Expand Up @@ -700,9 +701,14 @@ def _validate_field_modifications(self) -> None:
)

def _validate_meta_default_view(self) -> None:
default_view = self._global_config.meta.get(
"defaultView", data_ui_generator.DEFAULT_VIEW
)
default_view = self._global_config.meta.get("defaultView")
if (
default_view == "configuration"
and not self._global_config.has_configuration()
):
raise GlobalConfigValidatorException(
'meta.defaultView == "configuration" but there is no configuration defined in globalConfig'
)
if default_view == "inputs" and not self._global_config.has_inputs():
raise GlobalConfigValidatorException(
'meta.defaultView == "inputs" but there is no inputs defined in globalConfig'
Expand All @@ -715,8 +721,8 @@ def _validate_meta_default_view(self) -> None:
def validate(self) -> None:
self._validate_config_against_schema()
self._validate_configuration_tab_table_has_name_field()
self._validate_custom_rest_handlers()
self._validate_file_type_entity()
self._validate_custom_rest_handlers()
self._validate_validators()
self._validate_multilevel_menu()
self._validate_duplicates()
Expand Down
3 changes: 0 additions & 3 deletions splunk_add_on_ucc_framework/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2677,9 +2677,6 @@
"$ref": "#/definitions/DashboardPage"
}
},
"required": [
"configuration"
],
"additionalProperties": false
},
"RegexValidator": {
Expand Down
Loading

0 comments on commit 0f67e30

Please sign in to comment.