diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 22a085f07..23fdb6851 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,20 +1,29 @@ **Issue number:** +### PR Type + +**What kind of change does this PR introduce?** +* [ ] Feature +* [ ] Bug Fix +* [ ] Refactoring (no functional or API changes) +* [ ] Documentation Update +* [ ] Maintenance (dependency updates, CI, etc.) + ## Summary ### Changes -> Please provide a summary of what's being changed +Please provide a summary of the changes. ### User experience -> Please describe what the user experience looks like before and after this change +Please describe the user experience before and after this change. Screenshots are welcome for additional context. ## Checklist -If your change doesn't seem to apply, please leave them unchecked. +If an item doesn't apply to your changes, leave it unchecked. -* [ ] I have performed a self-review of this change -* [ ] Changes have been tested +* [ ] I have performed a self-review of this change according to the [development guidelines](https://splunk.github.io/addonfactory-ucc-generator/contributing/#development-guidelines) +* [ ] 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 -* [ ] PR title follows [conventional commit semantics](https://www.conventionalcommits.org/en/v1.0.0/) +* [ ] PR title and description follows the [contributing principles](https://splunk.github.io/addonfactory-ucc-generator/contributing/#pull-requests) diff --git a/.github/workflows/automatic_doc_generation.yml b/.github/workflows/automatic_doc_generation.yml index c65561ce5..45444cd5e 100644 --- a/.github/workflows/automatic_doc_generation.yml +++ b/.github/workflows/automatic_doc_generation.yml @@ -44,4 +44,4 @@ jobs: if: ${{ env.isPR }} run: | git add \*.md - git diff --staged --exit-code || (git commit -S -m "doc: updated docs regarding generated conf, xml and html files" && git push) + git diff --staged --exit-code || (git commit -S -m "docs: updated docs regarding generated conf, xml and html files" && git push) diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index b0580c2c9..98f67b57e 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -135,6 +135,7 @@ jobs: - "3.9" - "3.10" - "3.11" + - "3.12" steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -168,6 +169,7 @@ jobs: - "3.9" - "3.10" - "3.11" + - "3.12" steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 diff --git a/.gitignore b/.gitignore index 02c884184..303e93381 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ pip-wheel-metadata/UNKNOWN.dist-info/top_level.txt pip-wheel-metadata/UNKNOWN.dist-info/METADATA .vscode/settings.json .DS_Store -.venv +.venv* output # The following files should never be checked into git but can not be in the # ignore file due to poetry issues diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 826fa9337..fdc2c1b12 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -33,6 +33,10 @@ MD025: false # MD028/no-blanks-blockquote MD028: false +# MD029/ordered-list-item-prefix +MD029: + style: one + # MD033/no-inline-html MD033: false @@ -43,7 +47,7 @@ MD034: false MD040: false # MD041/first-line-heading/first-line-h1 -MD041: false +MD041: true # MD046/code-block-style MD046: false \ No newline at end of file diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 000000000..cc28b92ee --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +.github/PULL_REQUEST_TEMPLATE.md diff --git a/docs/additional_packaging.md b/docs/additional_packaging.md index 3edd98f63..ea63cead0 100644 --- a/docs/additional_packaging.md +++ b/docs/additional_packaging.md @@ -2,9 +2,22 @@ To extend the build process, you can create a `additional_packaging.py` file in the same file level where you have your globalConfig file. -This file should have the `additional_packaging` function, which accepts add-on name as its only argument. +This file should at least have: + +- the `cleanup_output_files` function, which accepts `output_path` (str), `add-on name` (str) as its arguments. +- the `additional_packaging` function, which accepts `add-on name` (str) as its only argument. + +First the `cleanup_output_files` function would be called from `ucc-gen` build process and then `additional_packaging` function. See the following example for proper usage: -* Build custom UI after `ucc-gen` finishes all its necessary steps. -* Use a workaround for a `ucc-gen` feature that has not been implemented. +- Build custom UI after `ucc-gen` finishes all its necessary steps. +- Use a workaround for a `ucc-gen` feature that has not been implemented. + +## Example + +Below is an example of additional_packaging.py containing both the implementations of functions. + +```python +--8<-- "tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py" +``` diff --git a/docs/advanced/custom_mapping.md b/docs/advanced/custom_mapping.md index 746b139b7..4449c731a 100644 --- a/docs/advanced/custom_mapping.md +++ b/docs/advanced/custom_mapping.md @@ -1,3 +1,5 @@ +# Custom Mapping + We can use this feature to map each field with meaningful value to display in the table. For example, the category field contains 1, 2, and 4 values, but when those values are displayed, the user might get confused as those values do not signify the meaning of their mapping. To avoid this confusion, the user can map each field with meaningful value as shown in the following example: If you have fields that are not mandatory but you would like to display them inside table, you can use default value option by providing ```"[[default]]"``` as one of parameters (check example bellow). It is a way to provide some meaningful information for form fields that have not been filled (fill empty cells in table). diff --git a/docs/advanced/custom_warning.md b/docs/advanced/custom_warning.md index d891fa153..51ae8a3ee 100644 --- a/docs/advanced/custom_warning.md +++ b/docs/advanced/custom_warning.md @@ -1,3 +1,5 @@ +# Custom Warning + This feature allows us to pass broarder description on Input and Configuration page displayed under main description. ### Warning Properties diff --git a/docs/advanced/dependent_dropdown.md b/docs/advanced/dependent_dropdown.md index b3c524a79..4f628d91e 100644 --- a/docs/advanced/dependent_dropdown.md +++ b/docs/advanced/dependent_dropdown.md @@ -1,3 +1,5 @@ +# Dependent Dropdown + This feature allows dynamic loading options for the `singleSelect` and the `multipleSelect` fields when the options for that field depend on other fields' values. It loads options via an API call to the endpoint mentioned in `endpointUrl` under options when any dependencies field is updated and all required dependencies fields are non-null. All non-required dependencies fields can be of any type, but all required dependencies fields should only be of single-select type. diff --git a/docs/advanced/groups_feature.md b/docs/advanced/groups_feature.md index 17234c8b7..1866e5538 100644 --- a/docs/advanced/groups_feature.md +++ b/docs/advanced/groups_feature.md @@ -1,3 +1,5 @@ +# Groups Feature + Using this functionality, the Inputs page form can be divided into distinct sections, each comprising relevant fields. If the `isExpandable` property is set to true in the global configuration file, the group will be in the [collapsible panel](https://splunkui.splunk.com/Packages/react-ui/CollapsiblePanel) type. The groups will be displayed at the bottom of the form. diff --git a/docs/advanced/oauth_support.md b/docs/advanced/oauth_support.md index 1d78a29ea..512ecf938 100644 --- a/docs/advanced/oauth_support.md +++ b/docs/advanced/oauth_support.md @@ -1,3 +1,5 @@ +# OAuth Support + UCC allows you to add Auth support in the configuration page. In UCC, OAuth2.0 of the Authorization Code Flow `grant` type is used. It only supports the standard parameters specified in [RFCP749](https://www.rfc-editor.org/rfc/rfc6749) for obtaining an authorization code. Auth can be used inside the entity tag. Use `type: "oauth"` in the entity list and specify the `options` next to the `type: "oauth"`. @@ -42,8 +44,9 @@ Auth can be used inside the entity tag. Use `type: "oauth"` in the entity list a + `disableonEdit`: When the form is in edit mode, the field becomes unable to be edited. The default value is false. + `enable`: The enable property sets whether a field is enabled or not. The default value is true. -> [!WARNING] -> The [Placeholder](https://splunkui.splunkeng.com/Packages/react-ui/Text?section=develop) attribute is deprecated and renounced. Instead, we recommend to use the "help" attribute. +!!! warning "Placeholder deprecation" + + The [Placeholder](https://splunkui.splunkeng.com/Packages/react-ui/Text?section=develop) attribute is deprecated and renounced. Instead, we recommend to use the "help" attribute. ### Usage diff --git a/docs/advanced/os-dependent_libraries.md b/docs/advanced/os-dependent_libraries.md index c387d1cfd..a8f97d38a 100644 --- a/docs/advanced/os-dependent_libraries.md +++ b/docs/advanced/os-dependent_libraries.md @@ -1,3 +1,5 @@ +# OS-dependent libraries + This feature allows you to download and unpack libraries with appropriate binaries for the indicated operating system during the build process. To do this, you need to expand the **meta** section in the global configuration with the **os-dependentLibraries** field. This field takes the following attributes: diff --git a/docs/advanced/save_validator.md b/docs/advanced/save_validator.md index eefa464d1..ebe42ef51 100644 --- a/docs/advanced/save_validator.md +++ b/docs/advanced/save_validator.md @@ -1,3 +1,5 @@ +# Save Validator + This feature allows you to pass a Javascript function as a string to apply customized validation to form data. By using this approach, you can write custom JavaScript code where you can write your business logic, and validating can return error messages which will be displayed at the top of the form. diff --git a/docs/advanced/sub_description.md b/docs/advanced/sub_description.md index f429d669b..bde4d1d9d 100644 --- a/docs/advanced/sub_description.md +++ b/docs/advanced/sub_description.md @@ -1,3 +1,5 @@ +# Sub Description + This feature allows us to pass a broader description on the Input and Configuration pages displayed under main description. ### Sub Descritpion Properties diff --git a/docs/alert_actions/adaptive_response.md b/docs/alert_actions/adaptive_response.md index b10433a4a..11fb9ac71 100644 --- a/docs/alert_actions/adaptive_response.md +++ b/docs/alert_actions/adaptive_response.md @@ -1,3 +1,5 @@ +# Adaptive Response + The Adaptive Response framework provides a mechanism for running preconfigured actions within the Splunk platform or by integrating with external applications. These actions can be automatically triggered by correlation search results or manually diff --git a/docs/alert_actions/alert_scripts.md b/docs/alert_actions/alert_scripts.md index 612f9cebb..66bbc4b8a 100644 --- a/docs/alert_actions/alert_scripts.md +++ b/docs/alert_actions/alert_scripts.md @@ -1,3 +1,5 @@ +# Alert Action Scripts + The following files would be created/ updated in the output folder once you executed the `ucc-gen` command: | File Location | Content Description | Action | diff --git a/docs/alert_actions/index.md b/docs/alert_actions/index.md index 7716b4487..924816858 100644 --- a/docs/alert_actions/index.md +++ b/docs/alert_actions/index.md @@ -2,6 +2,8 @@ title: Alert Actions --- +# Alert Actions + The alert action can help a user to take action on the alerts that have been triggered. The knowledge from Splunk can be sent to an outside service or to pull additional or detailed information related to the trigger details. An add-on can have multiple alert actions based on the use cases the add-on provides. You can know more about alert actions from [this documentation](https://docs.splunk.com/Documentation/Splunk/latest/Alert/Aboutalerts). diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 000000000..8e2c2c28c --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,117 @@ +# Commands + +These are the commands that are available in UCC framework. + +## `ucc-gen build` + +The `ucc-gen build` command builds the add-on. As of now, running `ucc-gen` does the same thing as running `ucc-gen build`, +but eventually calling `ucc-gen` without specifying a subcommand will be +deprecated. + +It takes the following parameters: + +* `--source` - [optional] folder containing the `app.manifest` and app + source. The default is `package`. +* `--config` - [optional] path to the configuration file. It defaults to + the globalConfig file in the parent directory of the source provided. + Only *.json* and *.yaml* files are accepted. +* `--ta-version` - [optional] override current version of TA. The default + version is version specified in `globalConfig.json` or `globalConfig.yaml`. + A Splunkbase compatible version of SEMVER is used by default. +* `-o` / `--output` - [optional] output folder to store the build add-on. + By default, it is saved in the `current directory/output` folder. + Absolute paths are accepted as well. +* `--python-binary-name` - [optional] Python binary name to use when + installing Python libraries. The default is `python3`. +* `-v` / `--verbose` - [optional] shows detailed information about + created/copied/modified/conflict files after build is complete. + This option is in the experimental mode. The default is `False`. +* `--pip-version` - [optional] pip version that is used to install python libraries. The default is `latest`. +* `--pip-legacy-resolver` - [optional] Use old pip dependency resolver by adding flag '--use-deprecated=legacy-resolver' + to pip install command. The default is`False`. + >**NOTE:** This flag is deprecated and will be removed from pip in the future. + Instead of using this flag, the correct solution would be to fix the packages your project depends on to work properly with the new resolver. Additionally, this flag is not compatible with pip version `23.2`. Use `23.2.1` instead. +* `--pip-custom-flag` - [optional] Additional flag(s) that will be added to the `pip install` command. + By default, all the following flags are added to the `pip install` command: `--no-compile`, `--prefer-binary` and `--ignore-installed`. + If `--pip-custom-flag` is specified these three arguments will be missing so if you still want them in your command add them to the `--pip-custom-flag` argument. + + Example: `--pip-custom-flag="--no-compile --prefer-binary --ignore-installed --report path/to/report.json --progress-bar on"` + +* `--ui-source-map` - [optional] if present generates front-end source maps (.js.map files), that helps with code debugging. + +### Verbose mode + +The verbose mode is available for `v5.35.0` and later. + +Running `ucc-gen build -v` or `ucc-gen build --verbose` prints additional information about +what was exactly created / copied / modified / conflicted after the build is complete. It does +not scan the `lib` folder due to the nature of the folder. + +See the following description of what each state means: + +* `created`: the file is not in the original package and was created during the build process. +* `copied`: the file is in the original package and was copied during the build process. +* `modified`: the file is in the original package and was modified during the build process. +* `conflict`: the file is in the original package and was copied during the build process, but may be generated by UCC itself, so incorrect usage can stop the add-on from working. + +## `ucc-gen init` + +`ucc-gen init` initializes the add-on. This is available on `v5.19.0` and later. +The `ucc-gen init` command initializes the add-on and bootstraps some code in the +modular input which you, as a developer, can extend for your needs. + +Apart from standard files needed for the add-on, it also adds search head +clustering files in the `default/server.conf` file and reload triggers in the +`default/app.conf` file. Those files will be soon generated automatically by the +`ucc-gen build` command itself. +during the add-on development. + +It takes the following parameters: + +* `--addon-name` - [required] add-on name. See the + [official naming convention guide](https://dev.splunk.com/enterprise/docs/releaseapps/splunkbase/namingguidelines/). +* `--addon-rest-root` - [optional] add-on REST root, defaults to `--addon-name` if not provided. +* `--addon-display-name` - [required] add-on "official" name. +* `--addon-input-name` - [required] name of the generated input. +* `--addon-version` - [optional] version of the generated add-on, with `0.0.1` by default. +* `--overwrite` - [optional] overwrites the already existing folder. + By default, you can't generate a new add-on to an already existing folder. + +## `ucc-gen import-from-aob` + +Import from AoB (Add-on Builder), from `v5.24.0` and later. It is in the +**experimental** state as of now, meaning that running this command may not +produce a 100% UCC compatible add-on, but we are going to work on future +improvements for the script itself. + +> **Note:** the `import-from-aob` command does not currently support Windows. + +The import functionality is based on the +[ucc_migration_test](https://github.com/tmartin14/ucc_migration_test) bash +script. +One of the ways you can use it is to download an AoB-based add-on from +Splunkbase, unarchive the folder, and then use +`ucc-gen import-from-aob --addon-name `. Or you can +run the same command against your locally developed add-on, but it should be +exported from AoB. + +It accepts the following parameters: + +* `--addon-name` - [required] add-on name. + +## `ucc-gen package` + +`ucc-gen package` can be used for `v5.30.0` and later. It packages the add-on so it can be installed. +It mimics the basics of the `slim package` command. This command can be used for most of the simple cases. + +It does not support: + +* the `.slimignore` file. +* the [dependencies section](https://dev.splunk.com/enterprise/docs/releaseapps/packageapps/packagingtoolkit/#Dependencies-section). + +It accepts the following parameters: + +* `--path` - [required] path to the built add-on (should include the `app.manifest` file). +* `-o` / `--output` - [optional] output folder to store the packaged add-on. + By default, it will be saved in the `current directory` folder. + It accepts absolute paths as well. diff --git a/docs/configurations/index.md b/docs/configurations/index.md index ffbf4687d..4f78f4524 100644 --- a/docs/configurations/index.md +++ b/docs/configurations/index.md @@ -1,3 +1,5 @@ +# Configuration + The `Configuration` tab can have multiple subtabs, for example, a tab for account configuration (to configure the account by adding account credentials), proxy configuration, and logging level configuration. @@ -18,6 +20,7 @@ proxy configuration, and logging level configuration. | name\* | string | To define the particular tab name. | | title\* | string | To show the title of the tab. | | [entity](../entity/index.md)\* | array | A list of fields and their properties. | +| [groups](../advanced/groups_feature.md) | array | It is used to divide forms into distinct sections, each comprising relevant fields. | | [table](../table.md) | object | To display accounts stanza in table | | style | string | By specifying this property in the global config file, the forms can either be opened as a new page or in a dialog.
Supported values are "page" or "dialog".
Default value is **dialog**. | | options | object | This property allows you to enable the [saveValidator](../advanced/save_validator.md) feature. | diff --git a/docs/configurations/logging.md b/docs/configurations/logging.md index 2177933ec..8a52e12cb 100644 --- a/docs/configurations/logging.md +++ b/docs/configurations/logging.md @@ -1,3 +1,5 @@ +# Logging + The logging tab is a predefined component that allows to create the page for changing the log level. It is added in the `pages.configuration.tabs` array diff --git a/docs/configurations/proxy.md b/docs/configurations/proxy.md index 7effd45d6..55dddd22a 100644 --- a/docs/configurations/proxy.md +++ b/docs/configurations/proxy.md @@ -1,3 +1,5 @@ +# Proxy + There are fields that need to be specified in order to enable proxy. ### Fields diff --git a/docs/contributing.md b/docs/contributing.md index 0074c0191..b4e61d9e2 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -2,6 +2,12 @@ We welcome contributions from the community! This guide will help you understand our contribution process and requirements. +## Development guidelines + +1. Small PRs ([blogpost](https://testing.googleblog.com/2024/07/in-praise-of-small-pull-requests.html)) +1. When fixing a bug, include a test that reproduces the issue in the same pull request (the test should fail without your changes) +1. If you are refactoring, ensure adequate test coverage exists for the target area. If coverage is insufficient, create tests in a separate pull request first. This approach provides a safety net for validating current behavior and simplifies code reviews. + ## Build and Test Prerequisites: @@ -47,7 +53,7 @@ UI tests will run automatically for any PR towards the `main` / `develop` branch poetry run ucc-gen build --source tests/testdata/test_addons/package_global_config_everything/package ``` -2. Install docker, and run containerized Splunk Enterprise using script: +1. Install docker, and run containerized Splunk Enterprise using script: ```bash ./scripts/run_splunk.sh @@ -55,9 +61,9 @@ UI tests will run automatically for any PR towards the `main` / `develop` branch There are mapped default Splunk ports to host. To use a different configuration, see [docker-splunk](https://splunk.github.io/docker-splunk/). Remember to mount the output package to the Splunk apps directory. -3. Install any browser specific to this browser driver, such as [chromedriver](https://chromedriver.chromium.org/getting-started/) for Chrome. +1. Install any browser specific to this browser driver, such as [chromedriver](https://chromedriver.chromium.org/getting-started/) for Chrome. -4. Run tests using the following command: +1. Run tests using the following command: ```bash poetry run pytest tests/ui @@ -85,49 +91,48 @@ UCC is a tool for Technology Add-ons (TAs), so it's important to test TA generat ### Overview 1. Install Dependencies for Your TA -2. Build the TA Using Your Local UCC Version -3. Package the TA into a .tar.gz file using `ucc-gen package` +1. Build the TA Using Your Local UCC Version +1. Package the TA into a .tar.gz file using `ucc-gen package` ### Installing TA Dependencies The method for installing dependencies may vary among different TAs. Common approaches include running Poetry, but please refer to your TA's documentation for specific instructions. ```bash -# Navigate to your TA repository -cd /path/to/your/ta +# These variables would be used in the further steps +ta_repo=/path/to/ta +ta_name=TA_Name_From_app.manifest -poetry install +poetry install --directory=$ta_repo -mkdir -p package/lib +mkdir -p $ta_repo/package/lib # Export dependencies to 'requirements.txt' -poetry export --without-hashes -o package/lib/requirements.txt +poetry export --without-hashes -o $ta_repo/package/lib/requirements.txt --directory $ta_repo ``` > Note: ucc-gen expects dependencies to be listed in `package/lib/requirements.txt`. ### Building TA -Run the following commands from the UCC repository: - ```bash -poetry run ucc-gen build --source /path/to/your/ta/package +poetry run ucc-gen build --source $ta_repo/package ``` -Ensure you specify the package folder, not the repository root. Monitor the build process for any errors. +Ensure you specify the `package` folder, not the repository root. Monitor the build process for any errors. **Caveat**: The build command may run scripts from the TA repository that may not be tested if running from a non-TA repository. For example, `build-ui.sh` may use relative paths for building custom components. You might need to manually run the script and/or copy the files to the output directory of UCC. ```bash -$ta_repo = /path/to/your/ta -$ta_name = TA_name +# in case if TA has custom UI components +mkdir -p output/$ta_name/appserver/static/js/build cp -a $ta_repo/output/$ta_name/appserver/static/js/build/custom output/$ta_name/appserver/static/js/build ``` ### Packaging TA ```bash -poetry run ucc-gen package --path output/TA_name +poetry run ucc-gen package --path output/$ta_name ``` This command will generate a packaged TA (.tar.gz file) that you can install into Splunk. diff --git a/docs/custom_ui_extensions/custom_cell.md b/docs/custom_ui_extensions/custom_cell.md index e0167f9d2..77df80dc8 100644 --- a/docs/custom_ui_extensions/custom_cell.md +++ b/docs/custom_ui_extensions/custom_cell.md @@ -1,3 +1,5 @@ +# Custom Cell + A Custom Cell is used to update the content of a table cell. `customCell` attribute will be used in the table header on the inputs and configuration page. diff --git a/docs/custom_ui_extensions/custom_control.md b/docs/custom_ui_extensions/custom_control.md index 5b51c2a46..5226aa0ca 100644 --- a/docs/custom_ui_extensions/custom_control.md +++ b/docs/custom_ui_extensions/custom_control.md @@ -1,3 +1,5 @@ +# Custom Control + The Custom Control feature allows you to display any customised input component in a form. The developer can easily design and render any complex input component with this feature. Modern add-ons frequently require the use of complex input components, and this feature will allow you to use the custom component in the form that is best suited to your needs, without relying on newer releases of UCC for support. ### Properties diff --git a/docs/custom_ui_extensions/custom_hook.md b/docs/custom_ui_extensions/custom_hook.md index 7726ddab3..2be4d8ea8 100644 --- a/docs/custom_ui_extensions/custom_hook.md +++ b/docs/custom_ui_extensions/custom_hook.md @@ -1,3 +1,5 @@ +# Custom Hook + Custom Hook is a JavaScript function that allows us to reuse some code throughout the app. It is used to validate form/dialog inputs. Hook is nothing more than a Javascript event handling on the events `onCreate`, `onChange`, `onRender`, `onSave`, `onSaveSuccess`, `onSaveFail`, and `onEditLoad`. diff --git a/docs/custom_ui_extensions/custom_menu.md b/docs/custom_ui_extensions/custom_menu.md index 4c70c47bc..1a679a61b 100644 --- a/docs/custom_ui_extensions/custom_menu.md +++ b/docs/custom_ui_extensions/custom_menu.md @@ -1,3 +1,5 @@ +# Custom Menu + Custom Menu can be created when there is more than one input present on the inputs page. > This feature is deprecated (will be removed in the next major version) as [`Multilevel Menu`](../inputs/multilevel_menu.md) is now ready to use if more than one input is available. diff --git a/docs/custom_ui_extensions/custom_row.md b/docs/custom_ui_extensions/custom_row.md index dd9a1c6b2..2446dd86b 100644 --- a/docs/custom_ui_extensions/custom_row.md +++ b/docs/custom_ui_extensions/custom_row.md @@ -1,3 +1,5 @@ +# Custom Row + When a row is expanded on the Inputs table or Configuration Table, Custom Row is utilized to incorporate a customized element. By clicking on the icon provided on the left side of each row, the input-specific details are displayed. ### Properties diff --git a/docs/custom_ui_extensions/custom_tab.md b/docs/custom_ui_extensions/custom_tab.md index 668d4afb7..8883452de 100644 --- a/docs/custom_ui_extensions/custom_tab.md +++ b/docs/custom_ui_extensions/custom_tab.md @@ -1,3 +1,5 @@ +# Custom Tab + Custom Tab feature can be used to render any customized UI component in the Configuration tabs. With this feature, you can design and render any complex input with ease. This is an advanced feature and can be leveraged with limitless functionalities. Modern add-ons are receiving complex use cases and this feature will allow you to design the UI perfectly for your case without having to depend on newer releases of UCC for support. ### Properties diff --git a/docs/custom_ui_extensions/overview.md b/docs/custom_ui_extensions/overview.md index 61e011d8d..5d2b6920f 100644 --- a/docs/custom_ui_extensions/overview.md +++ b/docs/custom_ui_extensions/overview.md @@ -1,3 +1,5 @@ +# Overview + The UCC package simplifies the deployment of React applications featuring the Splunk UI by eliminating the need for NodeJS, Yarn, or front-end dependencies installation. The core requirement for deployment is a `globalConfig.json` file. While UCC is designed to support a broad spectrum of use cases, there may be scenarios where the provided API options do not fully meet your needs. For such instances, UCC has a runtime custom JavaScript loading mechanism. This feature allows for the invocation of specific functionalities at pivotal moments within the application lifecycle, including `onChange` and `onRender` events. diff --git a/docs/dot_conf_files.md b/docs/dot_conf_files.md index 4f70536b5..b595cc8c8 100644 --- a/docs/dot_conf_files.md +++ b/docs/dot_conf_files.md @@ -1,17 +1,16 @@ # .conf files -`ucc-gen` generates the following `.conf` files in the `default` directory. -If any of the `.conf` files are present in the source directory, `ucc-gen` will -just copy that file to the output folder. The only exception is the `app.conf` file. +`ucc-gen build` generates the following `.conf` files in the `default` directory. +If any of the `.conf` files are present in the source directory, `ucc-gen` copies that file to the output folder. The only exception is the `app.conf` file. -> Note: for most of the use cases, the generated configuration is sufficient. If you -> need to adjust the file being generated, please ping us for a feature +> For most of the use cases, the generated configuration is sufficient. If you +> need to adjust the file that is generated, contact Splunk with a feature > request. Alternatively, create a file in the `default` location, so it will accepted > without being generated. ## `app.conf` -`ucc-gen` will merge the file present in the `default` folder with some +`ucc-gen` merges the file present in the `default` folder with some additional information generated during the build time. But if you don't need anything specific generated, you don't need to have `app.conf` in the source folder. @@ -19,26 +18,24 @@ app.conf uses the `app.manifest` file to determine the add-on description, the a the add-on title, and the add-on author (taking the first one if multiple are defined). Make sure that your `app.manifest` is up-to-date, so `app.conf` will have all relevant information. -Also, the `triggers` stanza will be created by `ucc-gen`. It will determine what +Also the `triggers` stanza is created by the `ucc-gen build` command. `ucc-gen build` determines what the `.conf` files are used in the add-on and generates the relevant key-value pairs. ## `inputs.conf` -`ucc-gen` will generate a stanza for every input defined in the `globalConfig` -file and set `python.version` to `python3`. +`ucc-gen` generates a stanza for every input defined in the `globalConfig` +file and sets `python.version` to `python3`. ## `server.conf` -`ucc-gen` will generate the `shclustering` stanza, which will determine what +`ucc-gen` generates the `shclustering` stanza. This stanza determines which `.conf` files are used in the add-on and generates the relevant key-value pairs. ## `web.conf` -`ucc-gen` will generate all needed information about the exposed endpoints -from the add-on. +`ucc-gen` generates information about the exposed endpoints from the add-on. ## `restmap.conf` -`ucc-gen` will generate all necessary information about the configuration of every -endpoint. +`ucc-gen` generates information about the configuration of every endpoint. diff --git a/docs/entity/components.md b/docs/entity/components.md index 354489243..4c1aa1a43 100644 --- a/docs/entity/components.md +++ b/docs/entity/components.md @@ -582,7 +582,7 @@ All attributes provided: Index field has two internal validators: 1. REGEX that forces index names to start with a letter or digit and can only contain letters, numbers, underscores or hyphens. -2. LENGTH which allows for an index name to have of 1 to 80 characters. +1. LENGTH which allows for an index name to have of 1 to 80 characters. `endpointUrl` for that entity is `data/indexes?search=isInternal=0+disabled=0` diff --git a/docs/entity/index.md b/docs/entity/index.md index f02b08681..6fe8ade7d 100644 --- a/docs/entity/index.md +++ b/docs/entity/index.md @@ -2,6 +2,8 @@ title: Entity --- +# Entity + ## Entity Properties | Property | Type | Description | Default Value | @@ -18,9 +20,10 @@ title: Entity | [validators](./validators.md) | array | It is used to validate the values of fields using various validators. It is strongly advised to specify validators for every entity. | - | | [modifyFieldsOnValue](./modifyFieldsOnValue.md) | array | It is used to specify values and parameters that will influence visually other entities. | - | -> [!WARNING] -> The [Placeholder](https://splunkui.splunk.com/Packages/react-ui/Text?section=develop) attribute is deprecated and renounced. -> The placeholder text is no longer displayed in the UI. Instead, use the `help` attribute. +!!! warning "Placeholder deprecation" + + The [Placeholder](https://splunkui.splunk.com/Packages/react-ui/Text?section=develop) attribute is deprecated and renounced. + The placeholder text is no longer displayed in the UI. Instead, use the `help` attribute. ## Common Options diff --git a/docs/entity/modifyFieldsOnValue.md b/docs/entity/modifyFieldsOnValue.md index b58bf2183..f379f1125 100644 --- a/docs/entity/modifyFieldsOnValue.md +++ b/docs/entity/modifyFieldsOnValue.md @@ -1,3 +1,5 @@ +# Modify Fields On Change + This feature allows to specify conditions to modify other fields based on current field value change. ### Modification Object Properties diff --git a/docs/entity/validators.md b/docs/entity/validators.md index 1ae85c72b..21996e7b4 100644 --- a/docs/entity/validators.md +++ b/docs/entity/validators.md @@ -1,3 +1,5 @@ +# Validators + Validators define acceptable values for fields. !!! warning "" diff --git a/docs/generated_files.md b/docs/generated_files.md index 8f6e3801b..582f41de5 100644 --- a/docs/generated_files.md +++ b/docs/generated_files.md @@ -2,9 +2,7 @@ title: UCC framework generated files --- -Below table describes the files generated by UCC framework - -## File Description +The following table describes the files generated by UCC framework. | File Name | File Location | File Description | | ------------ | ------------ | ----------------- | diff --git a/docs/images/troubleshooting_error_example.png b/docs/images/troubleshooting_error_example.png new file mode 100644 index 000000000..0183a336b Binary files /dev/null and b/docs/images/troubleshooting_error_example.png differ diff --git a/docs/images/troubleshooting_input_error.png b/docs/images/troubleshooting_input_error.png new file mode 100644 index 000000000..6d0536ade Binary files /dev/null and b/docs/images/troubleshooting_input_error.png differ diff --git a/docs/index.md b/docs/index.md index b07a5e4ad..194f2f627 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,49 +1,39 @@ -# Overview +# About UCC -`splunk-add-on-ucc-framework` is a framework to generate UI-based Splunk -add-ons. It includes UI, REST handlers, Modular inputs, OAuth and Alert -action templates. +Universal Configuration Console (UCC) is a framework that simplifies the process of add-on creation for developers. You can use UCC to generate UI-based Splunk add-ons. UCC includes UI, REST handlers, modular inputs, OAuth, and alert action templates. -Only add-ons that use Python 3 are supported. +The UCC framework helps you to maintain consistency and a uniform look and feel across different add-ons. You can easily update and modify your add-ons. -It is available as a GitHub action here: - +The UCC framework is available as a GitHub action. See . -You can use [Splunk Extension for VSCode](https://marketplace.visualstudio.com/items?itemName=Splunk.splunk) -as well. +To work with UCC framework, you can also use Splunk Extension. It helps you to create, test, and debug the add-ons in a simple way. For more information, see [Visual Studio Code Extension for Splunk](https://marketplace.visualstudio.com/items?itemName=Splunk.splunk). -## What is UCC? +## Libraries -UCC stands for Universal Configuration Console. The purpose of having a -framework for add-on generation is to simplify the process of add-on -creation for developers. UCC 5 uses [SplunkUI](https://splunkui.splunk.com/), -which is a new UI framework based on React. The UCC UI repository can be found in the `ui` folder. +UCC-based add-ons are powered by the following Splunk libraries: -UCC-based add-ons are being powered by Splunk libraries: -[`solnlib`](https://github.com/splunk/addonfactory-solutions-library-python) and -[`splunktaucclib`](https://github.com/splunk/addonfactory-ucc-library). More -information [here](ucc_related_libraries.md). +* `solnlib`, see [https://github.com/splunk/addonfactory-solutions-library-python](https://github.com/splunk/addonfactory-solutions-library-python) +* `splunktaucclib`, see [https://github.com/splunk/addonfactory-ucc-library](https://github.com/splunk/addonfactory-ucc-library). -## Features +For more information, see [UCC-related libraries](ucc_related_libraries.md). -The `splunk-add-on-ucc-framework`: +> Note: Some specific Python libraries (such as `google-cloud-bigquery`) use `.so` files to operate. `pip` installs OS-specific versions of those `.so` files, which makes it impossible to use such add-ons on a Windows machine because it was built for macOS. -* generates UI (`appserver` folder). -* generates Python REST handlers to support UI CRUD operations (`bin` folder). -* generates [inputs](./inputs/index.md) and their [helper modules](./inputs/helper.md) -* generates OpenAPI description documents (`appserver/static/openapi.json` file) (for more information, see [here](openapi.md)). -* generates `.conf` files (more information, see [here](dot_conf_files.md)). -* installs Python requirements (`lib` folder). -* generate metadata files (`metadata` folder). -* generates the monitoring dashboard (for more information, see [here](dashboard.md)). -* it possibly extends the UI with custom codes (for more information, see [here](custom_ui_extensions/custom_hook.md)). -* it possibly extends the build process via a `additional_packaging.py` file (more information, [here](additional_packaging.md)). -* generates the necessary files defined for the Alert Action, if defined in globalConfig (for more informaiton, see [here](alert_actions/index.md)). +## What UCC generates -## Installation +When you use UCC to create an add-on, the following elements are generated and stored in the appropriate folders: -`splunk-add-on-ucc-framework` is available on [PyPI](https://pypi.org/project/splunk-add-on-ucc-framework/). +* UI is stored in the `appserver` folder, +* Python REST handlers that support UI CRUD operations are stored in the `bin` folder, +* inputs and their helper modules. For more information, see [Inputs](./inputs/index.md) and [Helper modules](./inputs/helper.md), +* OpenAPI description documents are stored in the `appserver/static/openapi.json` file. For more information, see [OpenAPI description document](openapi.md), +* `.conf` files. For more information, see [.conf files](dot_conf_files.md), +* Python requirements are installed in the `lib` folder, +* metadata files are stored in the `metadata` folder, +* the monitoring dashboard. For more information, see [Dashboard](dashboard.md), +* the necessary files defined for the alert action, if you defined the alert action in the `globalConfig` file. For more information, see [Alert actions](alert_actions/index.md). -## Caveats +You can extend your add-ons with the following files: -* Some specific Python libraries (such as `google-cloud-bigquery`) use `.so` files to operate. `pip` will install OS-specific versions of those `.so` files, which makes it impossible to use such add-ons on a Windows machine since it was built for macOS. +* to extend the UI, use custom codes. For more information, see [Custom hook](custom_ui_extensions/custom_hook.md). +* to extend the build process, use the `additional_packaging.py` file. For more information, see [additional_packaging.py file](additional_packaging.md). diff --git a/docs/inputs/helper.md b/docs/inputs/helper.md index 438b025b7..c26858220 100644 --- a/docs/inputs/helper.md +++ b/docs/inputs/helper.md @@ -2,6 +2,8 @@ title: Input Helper Module --- +# Input Helper Module + Input scripts are regenerated during every build step, in order to keep the arguments and options up to date with the global config. To not discard changes made by developers, additional helper modules were introduced. Those modules must contain diff --git a/docs/inputs/index.md b/docs/inputs/index.md index 3fd3f8bbe..cd32f4b78 100644 --- a/docs/inputs/index.md +++ b/docs/inputs/index.md @@ -2,6 +2,8 @@ title: Inputs --- +# Inputs + The input page stores configuration information for data collection. Multiple inputs can be created on the Inputs page. Developers are required to add services in the global config file to create a new Input. If multiple services are @@ -53,4 +55,4 @@ This is how the global configuration looks like without tabs ### Output - diff --git a/docs/inputs/multilevel_menu.md b/docs/inputs/multilevel_menu.md index db1a2bde9..ce510447b 100644 --- a/docs/inputs/multilevel_menu.md +++ b/docs/inputs/multilevel_menu.md @@ -1,3 +1,5 @@ +# Multi-level Menu + This feature allows us to organize the input services into different categories. As a result, each group/category will have a separate sub-menu that can include numerous types of input services. Inputs services can also belong to multiple groups/categories. Using the [Custom Hook](../custom_ui_extensions/custom_hook.md), `groupName` can be saved in any form field for a specific inputs service stanza. diff --git a/docs/inputs/tabs.md b/docs/inputs/tabs.md index 2b0b5ffd0..93528ecdf 100644 --- a/docs/inputs/tabs.md +++ b/docs/inputs/tabs.md @@ -1,3 +1,5 @@ +# Tabs + This feature allows you to separate inputs based on their service name. Use the tabs feature when multiple inputs services are provided in the global configuration file, and you want to display each input service in a separate tab (and table). The `table` property must be present in the services to use the tabs feature. diff --git a/docs/metadata.md b/docs/metadata.md index 227a3549d..17508db76 100644 --- a/docs/metadata.md +++ b/docs/metadata.md @@ -2,6 +2,8 @@ title: globalConfig Meta Data --- +# Metadata + Metadata contains general information about add-on build. ## Metadata Properties diff --git a/docs/openapi.md b/docs/openapi.md index db28484ad..de925b3dc 100644 --- a/docs/openapi.md +++ b/docs/openapi.md @@ -50,6 +50,7 @@ Check [swagger](https://swagger.io/) or [other tools](https://github.com/OAI/Ope docker run -p 8081:8080 swaggerapi/swagger-editor ``` + Then go to: http://localhost:8081/ 1. Load the OpenAPI description document (File > Import file) @@ -81,7 +82,7 @@ Make sure you clicked the Authorize button, gave the username and password, and ### Instruction 1. Go to the directory where you downloaded `openapi.json` file -2. Run the following command: `docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/openapi.json -g python -o /local/restapi_client` +1. Run the following command: `docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/openapi.json -g python -o /local/restapi_client` - make sure `openapi.json` is in the current directory - you can generate clients for other languages as well - run @@ -90,9 +91,9 @@ Make sure you clicked the Authorize button, gave the username and password, and to see the list of supported languages -3. The client should appear in `restapi_client`. Open that directory (`cd restapi_client`) -4. Install the client (`pip install .`) -5. See `README.md` for an example of usage +1. The client should appear in `restapi_client`. Open that directory (`cd restapi_client`) +1. Install the client (`pip install .`) +1. See `README.md` for an example of usage ### Troubleshooting diff --git a/docs/quickstart.md b/docs/quickstart.md index 2589914bf..917028ebf 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,225 +1,91 @@ -# Quickstart +# Getting started -## Prerequisites - -You need to have Python 3.7+ and Git available in your machine to be able to use the `ucc-gen` command. - -> Git is used to generate the add-on version from Git tags. Alternatively, you can use the `--ta-version` parameter. -To be able to create an add-on using the UCC framework, you need to have at least: +Install the UCC framework and start building your first add-on. Then you can build new add-ons from the existing ones. -* a `globalConfig` file (in `JSON` or `YAML` format, `JSON` is mostly used). -* a `package` folder. -* `app.manifest` in the `package` folder ([documentation here](https://dev.splunk.com/enterprise/reference/packagingtoolkit/pkgtoolkitappmanifest/)). - -The `app.manifest` file now is being validated. See [Splunk Packaging Toolkit app.manifest schema definition](https://dev.splunk.com/enterprise/reference/packagingtoolkit/pkgtoolkitappmanifest/#JSON-schema-200) for more details. +## Prerequisites -> If both the globalConfig.json and globalConfig.yaml files are present, then the globalConfig.json file will take precedence. +Make sure that the following software is installed on your machine: -The JSON schema for the `globalConfig` file can be found in the `splunk_add_on_ucc_framework/schema/schema.json` file. +* Python 3.7 or later +* Git -### Add-on naming convention +> **Note:** Git is used to generate the add-on version from the Git tags. Alternatively, you can use the `--ta-version` parameter and specify the version by yourself. -See [Naming conventions for apps and add-ons in Splunkbase](https://dev.splunk.com/enterprise/docs/releaseapps/splunkbase/namingguidelines/) -for help naming your add-on. +## Install -## Initialize new add-on +### Create and activate the virtual environment -> Initialization of the new add-on is available from version `5.19.0` and up of `ucc-gen`. +Depending on which operating system you use, follow one of the procedures: -The following commands are macOS and Linux specific. +#### Windows -* Set up and activate the Python virtual environment: +Set up the Python virtual environment: ```bash -python3 -m venv .venv -source .venv/bin/activate + python3 -m venv .venv ``` -* Install `splunk-add-on-ucc-framework`: +If you use cmd.exe, activate the virtual environment with the following command: ```bash -pip install splunk-add-on-ucc-framework +.venv\Scripts\activate.bat ``` -* Initialize the new add-on: +If you use PowerShell, activate the virtual environment with the following command: ```bash -ucc-gen init --addon-name "demo_addon_for_splunk" --addon-display-name "Demo Add-on for Splunk" --addon-input-name demo_input +.venv\Scripts\activate.ps1 ``` -The new add-on is located in the `demo_addon_for_splunk` folder and can be built using -the [the following commands](#build-the-already-existing-add-on): +#### macOS, Linux -## Build the already existing add-on +Set up and activate the Python virtual environment: -The following commands are macOS and Linux specific: +```bash + python3 -m venv .venv + source .venv/bin/activate +``` -* Set up and activate the Python virtual environment (skip if you already have an environment): +### Install UCC package + +Install UCC package, it is available on PyPI, see . ```bash -python3 -m venv .venv -source .venv/bin/activate +pip install splunk-add-on-ucc-framework ``` -* Install `splunk-add-on-ucc-framework` and `splunk-packaging-toolkit` (skip if you already installed the libraries): +## Create a new add-on + +### Initialize a new add-on ```bash -pip install splunk-add-on-ucc-framework splunk-packaging-toolkit +ucc-gen init --addon-name "demo_addon_for_splunk" --addon-display-name "Demo Add-on for Splunk" --addon-input-name demo_input ``` -> Note: `splunk-packaging-toolkit` does not work with Python 3.10+. +For more information about the add-ons naming convention, see [Naming conventions for apps and add-ons in Splunkbase](https://dev.splunk.com/enterprise/docs/releaseapps/splunkbase/namingguidelines/) -> Note: If you use UCC `v5.30.0+`, `ucc-gen package` can be used instead of `slim`. +The new add-on is located in the `demo_addon_for_splunk` folder. -* Run `ucc-gen build` and package it - -> Provide a `--ta-version=` parameter if this repository is not version controlled. +### Build the add-on ```bash -ucc-gen build -slim package output/ +ucc-gen build --source demo_addon_for_splunk/package ``` -> Please use `ucc-gen build` instead of `ucc-gen` if you are using UCC `v5.19.0` or higher. - -Now you should see an archive created on the same level as your `globalConfig.json`. - -Now you can go to Splunk and install this add-on using the generated archive. +### Package the add-on + +```bash +ucc-gen package --path output/ +``` -After validating that the add-on was loaded correctly and all the basic operations -are working, you can extend the functionality of the input by copying and -pasting the automatically generated modular inputs file into the `package/bin` -folder. The generated inputs use the -[Splunk SDK for Python](https://github.com/splunk/splunk-sdk-python). After you -update the modular input code, you can run `ucc-gen` again, and then `ucc-gen` will -use updated modular inputs from `package/bin` instead of generating new ones. +The archive is created on the same level as your `globalConfig.json` file. -## Commands +For more information regarding commands, see [Commands](commands.md). -### `ucc-gen build` +### Install the add-on -The `ucc-gen build` command builds the add-on. As of now, running `ucc-gen` does the same thing as running `ucc-gen build`, -but eventually calling `ucc-gen` without specifying a subcommand will be -deprecated. +Go to your Splunk app instance and install this add-on using the generated archive. -It takes the following parameters: +After you check that the add-on was loaded correctly and all the basic operations are working, you can extend the functionality of the input by copying and pasting the automatically generated modular inputs file into the `package/bin` folder. The generated inputs use the Splunk SDK for Python. See [https://github.com/splunk/splunk-sdk-python](https://github.com/splunk/splunk-sdk-python). -* `--source` - [optional] folder containing the `app.manifest` and app - source. The default is `package`. -* `--config` - [optional] path to the configuration file. It defaults to - the globalConfig file in the parent directory of the source provided. - Only *.json* and *.yaml* files are accepted. -* `--ta-version` - [optional] override current version of TA. The default - version is version specified in `globalConfig.json` or `globalConfig.yaml`. - A Splunkbase compatible version of SEMVER will be used by default. -* `-o` / `--output` - [optional] output folder to store the build add-on. - By default, it will be saved in the `current directory/output` folder. - Absolute paths are accepted as well. -* `--python-binary-name` - [optional] Python binary name to use when - installing Python libraries. The default is `python3`. -* `-v` / `--verbose` - [optional] shows detailed information about - created/copied/modified/conflict files after build is complete. - This option is in experimental mode. The default is `False`. -* `--pip-version` - [optional] pip version that will be used to install python libraries. The default is `latest`. -* `--pip-legacy-resolver` - [optional] Use old pip dependency resolver by adding flag '--use-deprecated=legacy-resolver' - to pip install command. The default is`False`. NOTE: This flag is deprecated and will be removed from pip in the future. - Instead of using this flag, the correct solution would be to fix the packages your project depends on to work properly with the new resolver. Additionally, this flag is not compatible with pip version `23.2`. Use `23.2.1` instead. -* `--ui-source-map` - [optional] if present generates front-end source maps (.js.map files), that helps with code debugging. - -#### Verbose mode - -Verbose mode is available for `v5.35.0` and up. - -Running `ucc-gen build -v` or `ucc-gen build --verbose` prints additional information about -what was exactly created / copied / modified / conflicted after the build is complete. It does -not scan the `lib` folder due to the nature of the folder. - -See the following explanation on what exactly each state means: - -* `created`: The file is not in the original package and was created during the build process. -* `copied`: The file is in the original package and was copied during the build process. -* `modified`: The file is in the original package and was modified during the build process. -* `conflict`: The file is in the original package and was copied during the build process, but may be generated by UCC itself, so incorrect usage can stop the add-on from working. - -### `ucc-gen init` - -`ucc-gen init` initializes the add-on. This is available on `v5.19.0` and up. -The `ucc-gen init` command initializes the add-on and bootstraps some code in the -modular input which you, as a developer, can extend for your needs. - -Apart from standard files needed for the add-on, it also adds search head -clustering files in the `default/server.conf` file and reload triggers in the -`default/app.conf` file. Those files will be soon by generated automatically by the -`ucc-gen build` command itself. For now, you need to include them manually -during the add-on development. - -It takes the following parameters: - -* `--addon-name` - [required] add-on name. See the - [official naming convention guide](https://dev.splunk.com/enterprise/docs/releaseapps/splunkbase/namingguidelines/). -* `--addon-rest-root` - [optional] add-on REST root, defaults to `--addon-name` if not provided. -* `--addon-display-name` - [required] add-on "official" name. -* `--addon-input-name` - [required] name of the generated input. -* `--addon-version` - [optional] version of the generated add-on, with `0.0.1` by default. -* `--overwrite` - [optional] overwrites the already existing folder. - By default, you can't generate a new add-on to an already existing folder. - -### `ucc-gen import-from-aob` - -Import from AoB (Add-on Builder), from `v5.24.0` and up. It is in the -**experimental** state as of now, meaning that running this command may not -produce a 100% UCC compatible add-on, but we are going to work on future -improvements for the script itself. - -> Note: the `import-from-aob` command does not currently support Windows. - -The import functionality is based on the -[ucc_migration_test](https://github.com/tmartin14/ucc_migration_test) bash -script. -One of the ways you can use it is to download an AoB-based add-on from -Splunkbase, unarchive the folder, and then use -`ucc-gen import-from-aob --addon-name `. Or you can -run the same command against your locally developed add-on, but it should be -exported from AoB. - -It accepts the following parameters: - -* `--addon-name` - [required] add-on name. - -### `ucc-gen package` - -`ucc-gen package` can be used for `v5.30.0` and up. It packages the add-on so it can be installed. -It mimics the basics of the `slim package` command. This command can be used for most of the simple cases. - -It does not support: - -* the `.slimignore` file. -* the [dependencies section](https://dev.splunk.com/enterprise/docs/releaseapps/packageapps/packagingtoolkit/#Dependencies-section). - -It accepts the following parameters: - -* `--path` - [required] path to the built add-on (should include the `app.manifest` file). -* `-o` / `--output` - [optional] output folder to store the packaged add-on. - By default, it will be saved in the `current directory` folder. - It accepts absolute paths as well. - -## `ucc-gen build` - -`ucc-gen build`: - -* cleans the output folder. -* retrieves the package ID of the add-on. -* copies the UCC template directory under the `output/` directory. -* copies the globalConfig.json or the globalConfig.yaml file to - the `output//appserver/static/js/build` directory. -* collects and installs the add-on's requirements into the - `output//lib` directory of add-on's package. -* replaces tokens in views. -* copies the add-on's `package/*` to the `output//*` directory. -* If an add-on requires some additional configurations in packaging, - then `ucc-gen` runs the code in the `additional_packaging.py` file as well. -* **NOTE:** For the add-on's requirements, the packages are installed according to following information: - - `lib/requirements.txt` installs Python3 compatible packages into the `output//lib`. - - It removes `setuptools*`, `bin*`, `pip*`, `distribute*`, and `wheel*` if they exist from `output//lib` - - It removes the execute bit from every file under `output//lib`. -* **NOTE:** The build won't be generated only when the add-on name in `meta[name]` of `globalConfig` and `info[id][name]` in `app.manifest` are not same. +After you update the modular input code, you can run `ucc-gen` again, and then `ucc-gen` uses updated modular inputs from `package/bin` instead of generating new ones. diff --git a/docs/table.md b/docs/table.md index 543f2b274..1d87f1d84 100644 --- a/docs/table.md +++ b/docs/table.md @@ -1,8 +1,10 @@ +# Table + This is a common feature that is used to display the account and input stanzas on the [Inputs](inputs/index.md) and [Configuration](configurations/index.md) pages, respectively. Tables include many built-in features such as sorting, filtering, and pagination. -### Properties +## Properties - `header`* (Array Objects) specifies the list of columns in the table. + `field`* is he name of the field where the column data will be displayed. @@ -16,7 +18,7 @@ Tables include many built-in features such as sorting, filtering, and pagination + [mapping](advanced/custom_mapping.md) is used to map field values to more meaningful values. - [customRow](custom_ui_extensions/custom_row.md) can be used to customise the moreInfo Component. -### List of built-in table fields for Modular Input +## List of built-in table fields for Modular Input If your add-on has multiple modular inputs and you want to show the input type of each one, use the following in-built field: @@ -25,7 +27,7 @@ If your add-on has multiple modular inputs and you want to show the input type o | serviceName | It indicates the name of the Input service to be displayed in the table, for example, "example_input_one". | | serviceTitle | It indicates the title of the Input service to be displayed in the table, for example, "Example Input One". | -### Usage +## Usage ```json "table": { @@ -77,7 +79,7 @@ If your add-on has multiple modular inputs and you want to show the input type o } ``` -### Output +## Output This is how it looks in the UI: diff --git a/docs/theme_overrides/partials/header.html b/docs/theme_overrides/partials/header.html new file mode 100644 index 000000000..cdc8022e9 --- /dev/null +++ b/docs/theme_overrides/partials/header.html @@ -0,0 +1,104 @@ + + +{% set class = "md-header" %} +{% if "navigation.tabs.sticky" in features %} + {% set class = class ~ " md-header--shadow md-header--lifted" %} +{% elif "navigation.tabs" not in features %} + {% set class = class ~ " md-header--shadow" %} +{% endif %} + + +
+ + + + {% if "navigation.tabs.sticky" in features %} + {% if "navigation.tabs" in features %} + {% include "partials/tabs.html" %} + {% endif %} + {% endif %} +
\ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index e8e492c89..a4c7180f1 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -52,3 +52,41 @@ To run it, copy the script to the add-on folder, and then read the instructions in the script itself. If it does not work for your particular case, feel free to adjust it yourself, or file a feature request for us to improve something. + +## Something went wrong + +![img.png](images/troubleshooting_input_error.png) + +During the creation of the add-on, an error, such as incorrect import, unhandled exception etc., can be made in the code of modinput scripts. +When the add-on is built, this error is not caught and a package can be installed in Splunk without any problems. However, when you try to enter +the add-on page, you will see the message presented above. As this message is not +clear, it is not helpful in finding the root cause of the problem. + +Fortunately, most errors are logged in Splunk on the internal indexes. One of these indexes is `_internal`. + +First, search the `_internal` index for the word `ERROR` or `stderr`. +Usually the source of these logs is the `splunkd` process, so it can be used to pre-filter data. + +`index = _internal source=*splunkd* ERROR` or `index = _internal source=*splunkd* stderr` + +In the case of small infrastructures, this simple query can return information you need. + +When the instances are large and they have many applications, each of them can have errors. +In addition, some errors are recorded only once when the add-on is installed, so it can be difficult to determine the correct time range. + +In this case, you can narrow your search to a specific add-on. This may not be easy because no unique parameter is logged +that would easily filter out data for a specific add-on. However, you can use the `scheme` parameter, which is directly correlated with the names of inputs in the add-on. + +The example of a logged error: +![img.png](images/troubleshooting_error_example.png) + +Schema logs the input name with a colon at the end, so use an asterisk after entering the name: + +`index = _internal source=*splunkd* (component=ModularInputs stderr) OR component=ExecProcessor (scheme IN (example_input_one*, example_input_two*, example_input_three_abc*))` + +Or you can use a shortened version, using the wildcard mechanic: + +`index = _internal source=*splunkd* (component=ModularInputs stderr) OR component=ExecProcessor (scheme IN (*example_input*))` + +Since in this example we are talking about errors coming from the modular input scripts, +another filtering factor, the `ModularInputs` component, is added. The `ExecProcessor` component is also included. It is responsible for running and managing scripts. diff --git a/docs/uccignore.md b/docs/uccignore.md index 4a6dce70c..d6041fcc0 100644 --- a/docs/uccignore.md +++ b/docs/uccignore.md @@ -1,5 +1,8 @@ # `.uccignore` file +!!! warning "Deprecation Notice" + This feature has been deprecated from UCC framework as of v5.53.0 as the feature is ambiguous. You can achieve the same functionality using [additional_packaging.py](./additional_packaging.md). The `cleanup_output_files` provides a feature to clean up files after the source code has been copied. + This feature can be used to remove files from the output **after** the UCC template files were copied and **before** the source of the add-on recursively overrides the output folder. diff --git a/docs/ui_tests_alert_actions_page.md b/docs/ui_tests_alert_actions_page.md index aa35bf684..77417dfd7 100644 --- a/docs/ui_tests_alert_actions_page.md +++ b/docs/ui_tests_alert_actions_page.md @@ -1 +1,3 @@ +# Alert Action Page + ::: tests.ui.test_alert_actions_page diff --git a/docs/ui_tests_config_page_account.md b/docs/ui_tests_config_page_account.md index 58250f689..c9d1ba9e1 100644 --- a/docs/ui_tests_config_page_account.md +++ b/docs/ui_tests_config_page_account.md @@ -1 +1,3 @@ +# Account + ::: tests.ui.test_configuration_page_account_tab diff --git a/docs/ui_tests_config_page_custom.md b/docs/ui_tests_config_page_custom.md index 89763b2b8..7dd473b73 100644 --- a/docs/ui_tests_config_page_custom.md +++ b/docs/ui_tests_config_page_custom.md @@ -1 +1,3 @@ +# Custom + ::: tests.ui.test_configuration_page_custom_tab diff --git a/docs/ui_tests_config_page_general.md b/docs/ui_tests_config_page_general.md index ab6e5055f..ef6f6a9f7 100644 --- a/docs/ui_tests_config_page_general.md +++ b/docs/ui_tests_config_page_general.md @@ -1 +1,3 @@ +# General + ::: tests.ui.test_configuration_page diff --git a/docs/ui_tests_config_page_logging.md b/docs/ui_tests_config_page_logging.md index af068e6e5..6fcbadd77 100644 --- a/docs/ui_tests_config_page_logging.md +++ b/docs/ui_tests_config_page_logging.md @@ -1 +1,3 @@ +# Logging + ::: tests.ui.test_configuration_page_logging_tab diff --git a/docs/ui_tests_config_page_proxy.md b/docs/ui_tests_config_page_proxy.md index 0304833d9..09c6ac396 100644 --- a/docs/ui_tests_config_page_proxy.md +++ b/docs/ui_tests_config_page_proxy.md @@ -1 +1,3 @@ +# Proxy + ::: tests.ui.test_configuration_page_proxy_tab diff --git a/docs/ui_tests_inputs_page.md b/docs/ui_tests_inputs_page.md index 10b9d19b2..9e5bb1180 100644 --- a/docs/ui_tests_inputs_page.md +++ b/docs/ui_tests_inputs_page.md @@ -1 +1,3 @@ +# Input Page + ::: tests.ui.test_input_page diff --git a/mkdocs.yml b/mkdocs.yml index 4943a9214..f034c1484 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ markdown_extensions: theme: name: "material" + custom_dir: docs/theme_overrides palette: - media: "(prefers-color-scheme: light)" scheme: default @@ -52,58 +53,61 @@ plugins: show_if_no_docstring: true filters: ["!^_"] show_source: true + - autorefs + - print-site # should be at the end nav: - - Home: "index.md" - - Quickstart: "quickstart.md" + - About UCC: "index.md" + - Getting started: "quickstart.md" + - Commands: "commands.md" - ".conf files": "dot_conf_files.md" - Generated files: "generated_files.md" - Inputs: - "inputs/index.md" - Introduction: "inputs/index.md" - Tabs: "inputs/tabs.md" - - Multi-level Menu: "inputs/multilevel_menu.md" - - Helper Module: "inputs/helper.md" + - Multi-level menu: "inputs/multilevel_menu.md" + - Helper module: "inputs/helper.md" - Configuration: - "configurations/index.md" - Introduction: "configurations/index.md" - Logging: "configurations/logging.md" - Proxy: "configurations/proxy.md" - Dashboard: "dashboard.md" - - Alert Actions: + - Alert actions: - "alert_actions/index.md" - - Alert Action Scripts: "alert_actions/alert_scripts.md" - - Adaptive Response: "alert_actions/adaptive_response.md" + - Alert action scripts: "alert_actions/alert_scripts.md" + - Adaptive response: "alert_actions/adaptive_response.md" - Entity: - "entity/index.md" - Introduction: "entity/index.md" - Components: "entity/components.md" - Validators: "entity/validators.md" - - Modify Fields On Change: "entity/modifyFieldsOnValue.md" + - Modify fields On change: "entity/modifyFieldsOnValue.md" - Table: "table.md" - Additional packaging: "additional_packaging.md" - - UCC Ignore: "uccignore.md" + - UCC ignore: "uccignore.md" - OpenAPI: "openapi.md" - UCC-related libraries: "ucc_related_libraries.md" - - Custom UI Extensions: + - Custom UI extensions: - Overview: "custom_ui_extensions/overview.md" - - Custom Hook: "custom_ui_extensions/custom_hook.md" - - Custom Control: "custom_ui_extensions/custom_control.md" - - Custom Row: "custom_ui_extensions/custom_row.md" - - Custom Cell: "custom_ui_extensions/custom_cell.md" - - Custom Menu: "custom_ui_extensions/custom_menu.md" - - Custom Tab: "custom_ui_extensions/custom_tab.md" + - Custom hook: "custom_ui_extensions/custom_hook.md" + - Custom control: "custom_ui_extensions/custom_control.md" + - Custom row: "custom_ui_extensions/custom_row.md" + - Custom cell: "custom_ui_extensions/custom_cell.md" + - Custom menu: "custom_ui_extensions/custom_menu.md" + - Custom tab: "custom_ui_extensions/custom_tab.md" - Advanced: - - Custom Mapping: "advanced/custom_mapping.md" - - Dependent Dropdown: "advanced/dependent_dropdown.md" - - OAuth Support: "advanced/oauth_support.md" - - Custom REST Handler: "advanced/custom_rest_handler.md" - - Groups Feature: "advanced/groups_feature.md" - - Save Validator: "advanced/save_validator.md" + - Custom mapping: "advanced/custom_mapping.md" + - Dependent dropdown: "advanced/dependent_dropdown.md" + - OAuth support: "advanced/oauth_support.md" + - Custom REST handler: "advanced/custom_rest_handler.md" + - Groups feature: "advanced/groups_feature.md" + - Save validator: "advanced/save_validator.md" - OS-dependent libraries: "advanced/os-dependent_libraries.md" - - Sub Description: "advanced/sub_description.md" - - Custom Warning: "advanced/custom_warning.md" + - Sub description: "advanced/sub_description.md" + - Custom warning: "advanced/custom_warning.md" - Troubleshooting: "troubleshooting.md" - Contributing: "contributing.md" - Changelog: "CHANGELOG.md" diff --git a/poetry.lock b/poetry.lock index e42aedc6f..0aade8ad5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -886,6 +886,20 @@ files = [ {file = "mkdocs_material_extensions-1.2.tar.gz", hash = "sha256:27e2d1ed2d031426a6e10d5ea06989d67e90bb02acd588bc5673106b5ee5eedf"}, ] +[[package]] +name = "mkdocs-print-site-plugin" +version = "2.3.6" +description = "MkDocs plugin that combines all pages into one, allowing for easy export to PDF and standalone HTML." +optional = false +python-versions = ">=3.6" +files = [ + {file = "mkdocs-print-site-plugin-2.3.6.tar.gz", hash = "sha256:82e5cabcfb7fe3074daecea018f28ccb4bff086f965e3103fe91019a76752f22"}, + {file = "mkdocs_print_site_plugin-2.3.6-py3-none-any.whl", hash = "sha256:01ccb1ceccc87f29e1612bebb77c3bf9980809fbce750fc2113f9d6acea589d4"}, +] + +[package.dependencies] +mkdocs-material = ">=7.3.0" + [[package]] name = "mkdocstrings" version = "0.22.0" @@ -1227,13 +1241,13 @@ pytest = ">=5.3" [[package]] name = "pytest-splunk-addon" -version = "5.4.0" +version = "5.4.1" description = "A Dynamic test tool for Splunk Apps and Add-ons" optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "pytest_splunk_addon-5.4.0-py3-none-any.whl", hash = "sha256:b2c6482bbeb4ce9dc225e487e32299431bc94cd17c74574a7e7126a63612c1ca"}, - {file = "pytest_splunk_addon-5.4.0.tar.gz", hash = "sha256:78212c0583a5c7c5101ee3690e5cf75dba1e7f4edb444255e2a3cc6eee0c6a5a"}, + {file = "pytest_splunk_addon-5.4.1-py3-none-any.whl", hash = "sha256:634156197954573d8efceca0bd54de9973936cd1601af1ade413b9718b205fb0"}, + {file = "pytest_splunk_addon-5.4.1.tar.gz", hash = "sha256:df7b07c16e1da3f0f3fd2a6d88779829ba7e5acdfe6631e18a683162d201d81b"}, ] [package.dependencies] @@ -1358,6 +1372,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1567,12 +1582,12 @@ files = [ [[package]] name = "splunk-sdk" -version = "2.0.2" +version = "2.1.0" description = "The Splunk Software Development Kit for Python." optional = false python-versions = "*" files = [ - {file = "splunk-sdk-2.0.2.tar.gz", hash = "sha256:d5ccf6e1b96e493b1399e071ef10f8337e0a39ac2b2acc73fdd375b87b4104cb"}, + {file = "splunk-sdk-2.1.0.tar.gz", hash = "sha256:63f9a259a7c84d0c3b0b32cae652365b03f0f926acdb894b51456005df74ae21"}, ] [package.dependencies] @@ -1752,5 +1767,5 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" -python-versions = "^3.7" -content-hash = "5ce229450418d58e52d0d6dc05f04b0ef7e36b0ef1f71c4ae37f4cb73659347d" +python-versions = ">=3.7,<3.13" +content-hash = "f0072f7b472f7556af341f351358bed09701afd1b0b014e296a4c5fec118c4c6" diff --git a/pyproject.toml b/pyproject.toml index 420de2bd5..f5ac09dc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,14 +32,20 @@ classifiers = [ "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Code Generators", - "License :: OSI Approved :: Apache Software License" + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] [tool.poetry.urls] "Bug Tracker" = "https://github.com/splunk/addonfactory-ucc-generator/issues" [tool.poetry.dependencies] -python = "^3.7" +python = ">=3.7,<3.13" jinja2 = ">=2,<4" addonfactory-splunk-conf-parser-lib = "^0.4.3" dunamai = "^1.22.0" @@ -58,6 +64,7 @@ pytest-splunk-addon-ui-smartx = "^5.3.0" pytest-rerunfailures = "^11.1.1" mkdocs-material = "^9.1.3" mkdocstrings = {version=">=0", extras=["python"]} +mkdocs-print-site-plugin = "^2.3.6" pytest-cov = "^4.0.0" covdefaults = "^2.3.0" xmldiff = "^2.6.3" diff --git a/renovate.json b/renovate.json index 57e1ba3b0..180f5c14f 100644 --- a/renovate.json +++ b/renovate.json @@ -85,9 +85,9 @@ }, { "description": "Ignore NodeJS", - "matchPackageNames": ["node"], + "matchPackageNames": ["node", "@types/node"], "matchManagers": ["npm"], - "matchDepTypes": [ "engines" ], + "matchDepTypes": [ "engines", "devDependencies" ], "enabled": false }, { @@ -95,6 +95,13 @@ "matchPackageNames": ["ubuntu"], "matchManagers": ["github-actions"], "enabled": false + }, + { + "description": "react-router-dom throws not-dismissible warnings", + "matchPackageNames": ["react-router-dom"], + "matchManagers": ["npm"], + "matchUpdateTypes": ["minor"], + "enabled": false } ] } diff --git a/scripts/quick_start_ui.sh b/scripts/quick_start_ui.sh index 6b8c06961..2ade29f01 100755 --- a/scripts/quick_start_ui.sh +++ b/scripts/quick_start_ui.sh @@ -52,7 +52,7 @@ docker run \ -e "SPLUNK_DISABLE_POPUPS=true" \ -d \ --pull=always \ - --name splunk splunk/splunk:${1:-"latest"} + --name $CONTAINER_NAME splunk/splunk:${1:-"latest"} echo -n "Waiting Splunk for run" until curl -Lsk "https://localhost:8088/services/collector/health" &>/dev/null ; do echo -n "." && sleep 5 ; done diff --git a/splunk_add_on_ucc_framework/commands/build.py b/splunk_add_on_ucc_framework/commands/build.py index aabcf555f..b6d94c9f4 100644 --- a/splunk_add_on_ucc_framework/commands/build.py +++ b/splunk_add_on_ucc_framework/commands/build.py @@ -176,6 +176,11 @@ def _get_ignore_list( if not os.path.exists(ucc_ignore_path): return [] else: + logger.warning( + "The `.uccignore` feature has been deprecated from UCC and is planned to be removed after May 2025. " + "To achieve the similar functionality use additional_packaging.py." + "\nRefer: https://splunk.github.io/addonfactory-ucc-generator/additional_packaging/." + ) with open(ucc_ignore_path) as ignore_file: ignore_list = ignore_file.readlines() ignore_list = [ @@ -410,6 +415,7 @@ def generate( pip_version: str = "latest", pip_legacy_resolver: bool = False, ui_source_map: bool = False, + pip_custom_flag: Optional[str] = None, ) -> None: logger.info(f"ucc-gen version {__version__} is used") logger.info(f"Python binary name to use: {python_binary_name}") @@ -494,6 +500,7 @@ def generate( os_libraries=global_config.os_libraries, pip_version=pip_version, pip_legacy_resolver=pip_legacy_resolver, + pip_custom_flag=pip_custom_flag, ) except SplunktaucclibNotFound as e: logger.error(str(e)) @@ -561,6 +568,7 @@ def generate( python_binary_name, pip_version=pip_version, pip_legacy_resolver=pip_legacy_resolver, + pip_custom_flag=pip_custom_flag, ) logger.info( f"Installed add-on requirements into {ucc_lib_target} from {source}" @@ -629,15 +637,24 @@ def generate( os.path.abspath(os.path.join(source, os.pardir, "additional_packaging.py")) ): sys.path.insert(0, os.path.abspath(os.path.join(source, os.pardir))) + try: + from additional_packaging import cleanup_output_files + + cleanup_output_files(output_directory, ta_name) + except ImportError: + logger.info( + "additional_packaging.py is present but does not have `cleanup_output_files`. Skipping clean-up." + ) + try: from additional_packaging import additional_packaging additional_packaging(ta_name) - except ImportError as e: - logger.exception( - "additional_packaging.py is present but not importable.", e + except ImportError: + logger.info( + "additional_packaging.py is present but does not have `additional_packaging`. " + "Skipping additional packaging." ) - raise e if global_config: logger.info("Generating OpenAPI file") diff --git a/splunk_add_on_ucc_framework/commands/openapi_generator/ucc_to_oas.py b/splunk_add_on_ucc_framework/commands/openapi_generator/ucc_to_oas.py index fd7c91990..b2cdeaa76 100644 --- a/splunk_add_on_ucc_framework/commands/openapi_generator/ucc_to_oas.py +++ b/splunk_add_on_ucc_framework/commands/openapi_generator/ucc_to_oas.py @@ -109,13 +109,25 @@ def __get_schema_object( if entity.field == "oauth": # check for oauth as basic authentication is also mentioned in oauth if "basic" in entity.options.auth_type: - schema_object.properties["auth_type"] = {"type": "string"} for fields in entity.options.basic: schema_object.properties[fields.field] = {"type": "string"} if hasattr(fields, "encrypted") and (fields.encrypted is True): schema_object.properties[fields.field][ "format" ] = "password" + if "oauth" in entity.options.auth_type: + for fields in entity.options.oauth: + schema_object.properties[fields.field] = {"type": "string"} + if hasattr(fields, "encrypted") and (fields.encrypted is True): + schema_object.properties[fields.field][ + "format" + ] = "password" + if len(entity.options.auth_type) == 2: + # As per documentation we can have 2 types of authentication defined in `auth_type` + schema_object.properties["auth_type"] = { + "type": "string", + "enum": ["basic", "oauth"], + } continue schema_object.properties[entity.field] = {"type": "string"} if hasattr(entity, "options") and hasattr( @@ -223,7 +235,11 @@ def __get_media_type_object_with_schema_ref( return oas.MediaTypeObject(schema=schema) -def __get_path_get(*, name: str, description: str) -> oas.OperationObject: +def __get_path_get( + *, + name: str, + description: str, +) -> oas.OperationObject: return oas.OperationObject( description=description, responses={ diff --git a/splunk_add_on_ucc_framework/generators/doc_generator.py b/splunk_add_on_ucc_framework/generators/doc_generator.py index fd6fa5128..e322554f2 100644 --- a/splunk_add_on_ucc_framework/generators/doc_generator.py +++ b/splunk_add_on_ucc_framework/generators/doc_generator.py @@ -25,9 +25,7 @@ def generate_docs() -> None: title: UCC framework generated files --- -Below table describes the files generated by UCC framework - -## File Description +The following table describes the files generated by UCC framework. """ ) doc_content.append("| File Name | File Location | File Description |") diff --git a/splunk_add_on_ucc_framework/install_python_libraries.py b/splunk_add_on_ucc_framework/install_python_libraries.py index 305f6d868..fa5e00783 100644 --- a/splunk_add_on_ucc_framework/install_python_libraries.py +++ b/splunk_add_on_ucc_framework/install_python_libraries.py @@ -151,6 +151,7 @@ def install_python_libraries( os_libraries: Optional[List[OSDependentLibraryConfig]] = None, pip_version: str = "latest", pip_legacy_resolver: bool = False, + pip_custom_flag: Optional[str] = None, ) -> None: path_to_requirements_file = os.path.join(source_path, "lib", "requirements.txt") if os.path.isfile(path_to_requirements_file): @@ -163,6 +164,7 @@ def install_python_libraries( installer=python_binary_name, pip_version=pip_version, pip_legacy_resolver=pip_legacy_resolver, + pip_custom_flag=pip_custom_flag, ) if includes_ui: _check_libraries_required_for_ui( @@ -202,6 +204,7 @@ def install_libraries( installer: str, pip_version: str = "latest", pip_legacy_resolver: bool = False, + pip_custom_flag: Optional[str] = None, ) -> None: """ Upgrades `pip` version to the latest one and installs requirements to the @@ -222,13 +225,16 @@ def install_libraries( sys.exit(1) deps_resolver = "--use-deprecated=legacy-resolver " if pip_legacy_resolver else "" + custom_flag = ( + pip_custom_flag + if pip_custom_flag + else "--no-compile --prefer-binary --ignore-installed " + ) pip_install_command = ( f'-r "{requirements_file_path}" ' - f"--no-compile " - f"--prefer-binary " - f"--ignore-installed " f"{deps_resolver}" - f'--target "{installation_path}"' + f'--target "{installation_path}" ' + f"{custom_flag}" ) _pip_install( installer=installer, command=pip_update_command, command_desc="pip upgrade" diff --git a/splunk_add_on_ucc_framework/main.py b/splunk_add_on_ucc_framework/main.py index 1091ad87c..71c86af6e 100644 --- a/splunk_add_on_ucc_framework/main.py +++ b/splunk_add_on_ucc_framework/main.py @@ -130,6 +130,13 @@ def main(argv: Optional[Sequence[str]] = None) -> int: help="Use old pip dependency resolver by adding flag '--use-deprecated=legacy-resolver' " "to pip install command.", ) + build_parser.add_argument( + "--pip-custom-flag", + help="Custom flag that will be add to pip install command", + type=str, + default=False, + required=False, + ) build_parser.add_argument( "--ui-source-map", help="Adds front-end source-map files .js.map", @@ -216,6 +223,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int: pip_version=args.pip_version, pip_legacy_resolver=args.pip_legacy_resolver, ui_source_map=args.ui_source_map, + pip_custom_flag=args.pip_custom_flag, ) if args.command == "package": package.package(path_to_built_addon=args.path, output_directory=args.output) diff --git a/splunk_add_on_ucc_framework/schema/schema.json b/splunk_add_on_ucc_framework/schema/schema.json index 26c4c9583..a99243630 100644 --- a/splunk_add_on_ucc_framework/schema/schema.json +++ b/splunk_add_on_ucc_framework/schema/schema.json @@ -2476,6 +2476,9 @@ }, "hideForPlatform": { "$ref": "#/definitions/HideForPlatform" + }, + "groups": { + "$ref": "#/definitions/Groups" } }, "anyOf": [ diff --git a/tests/smoke/__init__.py b/tests/smoke/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/smoke/test_ucc_build.py b/tests/smoke/test_ucc_build.py index 541f33d1e..22f8de63c 100644 --- a/tests/smoke/test_ucc_build.py +++ b/tests/smoke/test_ucc_build.py @@ -60,12 +60,9 @@ def test_ucc_generate_with_config_param(): Check if globalConfig and app.manifest contains current ucc version """ - def check_ucc_versions(): + def check_ucc_versions(parent_folder): global_config_path = path.join( - path.dirname(path.realpath(__file__)), - "..", - "..", - "output", + parent_folder, "Splunk_TA_UCCExample", "appserver", "static", @@ -95,10 +92,12 @@ def check_ucc_versions(): "package_global_config_everything", "globalConfig.json", ) + with tempfile.TemporaryDirectory(prefix="ucc") as temp: + build.generate( + source=package_folder, config_path=config_path, output_directory=temp + ) - build.generate(source=package_folder, config_path=config_path) - - check_ucc_versions() + check_ucc_versions(temp) def test_ucc_generate_with_everything(caplog): @@ -376,7 +375,7 @@ def extract_summary_logs(): if copy_logs: return_logs.append(record) - if record.message[:22] == message_to_end: + if record.message.startswith(message_to_end): copy_logs = False return return_logs @@ -465,8 +464,8 @@ def summarize_types(raw_expected_logs): def test_ucc_generate_with_everything_uccignore(caplog): """ - Checks the functioning of .uccignore present in a repo. - Compare only the files that shouldn't be present in the output directory. + Checks the deprecation warning of .uccignore present in a repo with + its functionality still working. """ with tempfile.TemporaryDirectory() as temp_dir: package_folder = path.join( @@ -477,6 +476,16 @@ def test_ucc_generate_with_everything_uccignore(caplog): "package_global_config_everything_uccignore", "package", ) + # create `.uccignore` temporarily + ucc_file = path.join(path.dirname(package_folder), ".uccignore") + f = open(ucc_file, "w+") + f.write( + """**/**one.py +bin/splunk_ta_uccexample_rh_example_input_two.py +bin/wrong_pattern +""" + ) + f.close() build.generate(source=package_folder, output_directory=temp_dir) expected_warning_msg = ( @@ -493,9 +502,22 @@ def test_ucc_generate_with_everything_uccignore(caplog): removed = set( caplog.text.split("Removed:", 1)[1].split("INFO")[0].strip().split("\n") ) + exp_msg = ( + "The `.uccignore` feature has been deprecated from UCC and is planned to be removed after May 2025. " + "To achieve the similar functionality use additional_packaging.py." + "\nRefer: https://splunk.github.io/addonfactory-ucc-generator/additional_packaging/." + ) + exp_info_msg = ( + "additional_packaging.py is present but does not have `additional_packaging`." + " Skipping additional packaging." + ) + assert exp_msg in caplog.text + assert exp_info_msg in caplog.text assert expected_warning_msg in caplog.text assert edm_paths == removed + # on successful assertion, we delete the file + os.remove(ucc_file) actual_folder = path.join(temp_dir, "Splunk_TA_UCCExample") # when custom files are provided, default files shouldn't be shipped @@ -509,6 +531,36 @@ def test_ucc_generate_with_everything_uccignore(caplog): assert not path.exists(actual_file_path) +def test_ucc_generate_with_everything_cleanup_output_files(): + """ + Checks the functioning of addtional_packaging.py's `cleanup_output_files` present in a repo. + Compares only the files that shouldn't be present in the output directory. + """ + with tempfile.TemporaryDirectory() as temp_dir: + package_folder = path.join( + path.dirname(path.realpath(__file__)), + "..", + "testdata", + "test_addons", + "package_global_config_everything_uccignore", + "package", + ) + build.generate(source=package_folder, output_directory=temp_dir) + + actual_folder = path.join(temp_dir, "Splunk_TA_UCCExample") + # when custom files are provided, default files shouldn't be shipped + files_should_be_absent = [ + ("bin", "example_input_one.py"), + ("bin", "splunk_ta_uccexample_rh_example_input_one.py"), + ("bin", "splunk_ta_uccexample_rh_example_input_two.py"), + ("default", "redundant.conf"), + ("default", "nav", "views", "file_copied_from_source_code.xml"), + ] + for af in files_should_be_absent: + actual_file_path = path.join(actual_folder, *af) + assert not path.exists(actual_file_path) + + def test_ucc_generate_only_one_tab(): package_folder = path.join( path.dirname(path.realpath(__file__)), diff --git a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/appserver/static/openapi.json b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/appserver/static/openapi.json index 65dc6e3ef..d119bd205 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/appserver/static/openapi.json +++ b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/appserver/static/openapi.json @@ -50,9 +50,6 @@ "account_multiple_select": { "type": "string" }, - "auth_type": { - "type": "string" - }, "username": { "type": "string" }, @@ -63,6 +60,23 @@ "token": { "type": "string", "format": "password" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, + "auth_type": { + "type": "string", + "enum": [ + "basic", + "oauth" + ] } } }, @@ -89,9 +103,6 @@ "account_multiple_select": { "type": "string" }, - "auth_type": { - "type": "string" - }, "username": { "type": "string" }, @@ -102,6 +113,23 @@ "token": { "type": "string", "format": "password" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, + "auth_type": { + "type": "string", + "enum": [ + "basic", + "oauth" + ] } } }, 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 708061c18..877e52159 100644 --- a/tests/testdata/test_addons/package_global_config_everything/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_everything/globalConfig.json @@ -474,6 +474,21 @@ } } ], + "groups": [ + { + "label": "Hidden entities", + "fields": [ + "text_field_hidden_for_cloud", + "text_field_hidden_for_enterprise", + "field_no_validators", + "field_no_validators_suppressed" + ], + "options": { + "isExpandable": true, + "expand": false + } + } + ], "title": "Account", "restHandlerModule": "splunk_ta_uccexample_validate_account_rh", "restHandlerClass": "CustomAccountValidator" @@ -1891,10 +1906,10 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.51.1+3536793cd", + "version": "5.52.0+70c7e9d6b", "displayName": "Splunk UCC test Add-on", "schemaVersion": "0.0.9", - "_uccVersion": "5.51.1", + "_uccVersion": "5.52.0", "supportedThemes": [ "light", "dark" diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/.uccignore b/tests/testdata/test_addons/package_global_config_everything_uccignore/.uccignore deleted file mode 100644 index 9ab92ea97..000000000 --- a/tests/testdata/test_addons/package_global_config_everything_uccignore/.uccignore +++ /dev/null @@ -1,4 +0,0 @@ -**/**one.py - -bin/splunk_ta_uccexample_rh_example_input_two.py -bin/wrong_pattern diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py b/tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py new file mode 100644 index 000000000..8af85b880 --- /dev/null +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/additional_packaging.py @@ -0,0 +1,22 @@ + +from os.path import sep, exists, dirname, realpath, join +from os import remove, system, _exit, WEXITSTATUS + +def cleanup_output_files(output_path: str, ta_name: str) -> None: + """ + prepare a list for the files to be deleted after the source code has been copied to output directory + """ + files_to_delete = [] + files_to_delete.append(sep.join([output_path, ta_name, "default", "redundant.conf"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "splunk_ta_uccexample_rh_example_input_two.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "example_input_one.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "splunk_ta_uccexample_rh_example_input_one.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "bin", "file_does_not_exist.py"])) + files_to_delete.append(sep.join([output_path, ta_name, "default", "nav", "views", "file_copied_from_source_code.xml"])) + + for delete_file in files_to_delete: + try: + remove(delete_file) + except (FileNotFoundError): + # simply pass if the file doesn't exist + pass diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/package/default/nav/views/file_copied_from_source_code.xml b/tests/testdata/test_addons/package_global_config_everything_uccignore/package/default/nav/views/file_copied_from_source_code.xml new file mode 100644 index 000000000..5c26408da --- /dev/null +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/package/default/nav/views/file_copied_from_source_code.xml @@ -0,0 +1,6 @@ + + World + Python + Hello + Hello World! + \ No newline at end of file diff --git a/tests/testdata/test_addons/package_global_config_everything_uccignore/package/default/redundant.conf b/tests/testdata/test_addons/package_global_config_everything_uccignore/package/default/redundant.conf new file mode 100644 index 000000000..c6c70d719 --- /dev/null +++ b/tests/testdata/test_addons/package_global_config_everything_uccignore/package/default/redundant.conf @@ -0,0 +1,4 @@ +[redundant] +key = value +content_type = json +parser = json \ No newline at end of file diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/commands/openapi_generator/test_ucc_to_oas.py b/tests/unit/commands/openapi_generator/test_ucc_to_oas.py index 208659e2f..15099822f 100644 --- a/tests/unit/commands/openapi_generator/test_ucc_to_oas.py +++ b/tests/unit/commands/openapi_generator/test_ucc_to_oas.py @@ -24,3 +24,17 @@ def test_transform_multiple_account( "openapi.json.multiple_account.generated" ) assert json.loads(expected_open_api_json) == openapi_object.json + + +def test_transform_one_auth_type( + global_config_single_authentication, app_manifest_correct +): + global_config_single_authentication.expand() + openapi_object = ucc_to_oas.transform( + global_config_single_authentication, app_manifest_correct + ) + + expected_open_api_json = get_testdata_file( + "openapi.json.single_authentication.generated" + ) + assert json.loads(expected_open_api_json) == openapi_object.json diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 7a926b04e..99ade67a7 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -65,6 +65,15 @@ def global_config_multiple_account() -> global_config_lib.GlobalConfig: return global_config +@pytest.fixture +def global_config_single_authentication() -> global_config_lib.GlobalConfig: + global_config_path = helpers.get_testdata_file_path( + "valid_single_authentication_config.json" + ) + global_config = global_config_lib.GlobalConfig(global_config_path) + return global_config + + @pytest.fixture() def os_dependent_library_config(): return lambda name="lib1", python_version="37", target="t", os="os": OSDependentLibraryConfig( diff --git a/tests/unit/generators/test_doc_generator.py b/tests/unit/generators/test_doc_generator.py index 0b6ac311f..820b76b29 100644 --- a/tests/unit/generators/test_doc_generator.py +++ b/tests/unit/generators/test_doc_generator.py @@ -34,9 +34,7 @@ def test_generate_docs(mock_open, mock_dirname, mock_realpath): "title: UCC framework generated files", "---", "", - "Below table describes the files generated by UCC framework", - "", - "## File Description", + "The following table describes the files generated by UCC framework.", "", "| File Name | File Location | File Description |", "| ------------ | ------------ | ----------------- |", @@ -73,9 +71,7 @@ def test_generate_docs_empty_list(mock_open_file, mock_dirname, mock_realpath): "title: UCC framework generated files", "---", "", - "Below table describes the files generated by UCC framework", - "", - "## File Description", + "The following table describes the files generated by UCC framework.", "", "| File Name | File Location | File Description |", "| ------------ | ------------ | ----------------- |", diff --git a/tests/unit/test_global_config_update.py b/tests/unit/test_global_config_update.py index 9f2ab16d8..32fe4c091 100644 --- a/tests/unit/test_global_config_update.py +++ b/tests/unit/test_global_config_update.py @@ -11,6 +11,7 @@ _dump_with_migrated_tabs, _dump_with_migrated_entities, _stop_build_on_placeholder_usage, + _dump_enable_from_global_config, ) from splunk_add_on_ucc_framework.entity import IntervalEntity from splunk_add_on_ucc_framework import global_config as global_config_lib @@ -207,3 +208,31 @@ def test_config_validation_when_placeholder_is_present(tmp_path, caplog): expected_schema_version = "0.0.7" assert expected_schema_version == global_config.schema_version assert error_log in caplog.text + + +def test_dump_enable_from_global_config_enable_present(tmp_path, caplog): + tmp_file_gc = tmp_path / "globalConfig.json" + + helpers.copy_testdata_gc_to_tmp_file( + tmp_file_gc, "valid_config_input_with_enable_action.json" + ) + global_config = global_config_lib.GlobalConfig(str(tmp_file_gc)) + expected_schema_version = "0.0.9" + + _dump_enable_from_global_config(global_config) + + assert expected_schema_version == global_config.schema_version + assert "`enable` attribute found in input's page table action." in caplog.text + + +def test_dump_enable_from_global_config_enable_absent(tmp_path, caplog): + tmp_file_gc = tmp_path / "globalConfig.yaml" + + helpers.copy_testdata_gc_to_tmp_file(tmp_file_gc, "valid_config.yaml") + global_config = global_config_lib.GlobalConfig(str(tmp_file_gc)) + expected_schema_version = "0.0.9" + + _dump_enable_from_global_config(global_config) + + assert expected_schema_version == global_config.schema_version + assert caplog.text == "" diff --git a/tests/unit/test_install_python_libraries.py b/tests/unit/test_install_python_libraries.py index f14cc0891..8e2d4972a 100644 --- a/tests/unit/test_install_python_libraries.py +++ b/tests/unit/test_install_python_libraries.py @@ -40,8 +40,8 @@ def test_install_libraries(mock_subprocess_run): expected_install_command = ( 'python3 -m pip install -r "package/lib/requirements.txt"' - " --no-compile --prefer-binary --ignore-installed " - '--target "/path/to/output/addon_name/lib"' + ' --target "/path/to/output/addon_name/lib" ' + "--no-compile --prefer-binary --ignore-installed " ) expected_pip_update_command = "python3 -m pip install --upgrade pip" mock_subprocess_run.assert_has_calls( @@ -413,8 +413,8 @@ def test_install_libraries_custom_pip(mock_subprocess_run): expected_install_command = ( 'python3 -m pip install -r "package/lib/requirements.txt"' - " --no-compile --prefer-binary --ignore-installed " - '--target "/path/to/output/addon_name/lib"' + ' --target "/path/to/output/addon_name/lib" ' + "--no-compile --prefer-binary --ignore-installed " ) expected_pip_update_command = "python3 -m pip install --upgrade pip==21.666.666" mock_subprocess_run.assert_has_calls( @@ -442,8 +442,8 @@ def test_install_libraries_legacy_resolver(mock_subprocess_run): expected_install_command = ( 'python3 -m pip install -r "package/lib/requirements.txt"' - " --no-compile --prefer-binary --ignore-installed " - '--use-deprecated=legacy-resolver --target "/path/to/output/addon_name/lib"' + ' --use-deprecated=legacy-resolver --target "/path/to/output/addon_name/lib" ' + "--no-compile --prefer-binary --ignore-installed " ) expected_pip_update_command = "python3 -m pip install --upgrade pip" mock_subprocess_run.assert_has_calls( @@ -507,3 +507,61 @@ def test_validate_conflicting_paths_empty_list(): def test_is_pip_lib_installed_wrong_arguments(): with pytest.raises(InvalidArguments): _pip_is_lib_installed("i", "t", "l", allow_higher_version=True) + + +@mock.patch("subprocess.run", autospec=True) +def test_install_libraries_pip_custom_flag(mock_subprocess_run): + mock_subprocess_run.return_value = MockSubprocessResult(0) + + install_libraries( + "package/lib/requirements.txt", + "/path/to/output/addon_name/lib", + "python3", + pip_custom_flag="--report path/to/json.json", + ) + + expected_install_command = ( + 'python3 -m pip install -r "package/lib/requirements.txt"' + ' --target "/path/to/output/addon_name/lib" --report path/to/json.json' + ) + expected_pip_update_command = "python3 -m pip install --upgrade pip" + mock_subprocess_run.assert_has_calls( + [ + mock.call( + expected_pip_update_command, shell=True, env=None, capture_output=True + ), + mock.call( + expected_install_command, shell=True, env=None, capture_output=True + ), + ] + ) + + +@mock.patch("subprocess.run", autospec=True) +def test_install_libraries_multiple_pip_custom_flags(mock_subprocess_run): + mock_subprocess_run.return_value = MockSubprocessResult(0) + + install_libraries( + "package/lib/requirements.txt", + "/path/to/output/addon_name/lib", + "python3", + pip_custom_flag="--no-compile --prefer-binary --ignore-installed " + "--report path/to/json.json --progress-bar on --require-hashes", + ) + + expected_install_command = ( + 'python3 -m pip install -r "package/lib/requirements.txt"' + ' --target "/path/to/output/addon_name/lib" --no-compile --prefer-binary --ignore-installed ' + "--report path/to/json.json --progress-bar on --require-hashes" + ) + expected_pip_update_command = "python3 -m pip install --upgrade pip" + mock_subprocess_run.assert_has_calls( + [ + mock.call( + expected_pip_update_command, shell=True, env=None, capture_output=True + ), + mock.call( + expected_install_command, shell=True, env=None, capture_output=True + ), + ] + ) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 7f083a14c..6b8d187eb 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -19,6 +19,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -33,6 +34,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -47,6 +49,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -61,6 +64,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -75,6 +79,7 @@ "verbose_file_summary_report": True, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -96,6 +101,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -119,6 +125,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -144,6 +151,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -170,6 +178,7 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -197,6 +206,7 @@ "verbose_file_summary_report": True, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -226,6 +236,7 @@ "verbose_file_summary_report": True, "pip_version": "21.0.0", "pip_legacy_resolver": False, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -256,6 +267,7 @@ "verbose_file_summary_report": True, "pip_version": "21.0.0", "pip_legacy_resolver": True, + "pip_custom_flag": False, "ui_source_map": False, }, ), @@ -278,6 +290,32 @@ "verbose_file_summary_report": False, "pip_version": "latest", "pip_legacy_resolver": False, + "pip_custom_flag": False, + "ui_source_map": True, + }, + ), + ( + [ + "--source", + "package", + "--ta-version", + "2.2.0", + "--python-binary-name", + "python.exe", + "--ui-source-map", + "--pip-custom-flag", + "--progress-bar on", + ], + { + "source": "package", + "config_path": None, + "addon_version": "2.2.0", + "output_directory": None, + "python_binary_name": "python.exe", + "verbose_file_summary_report": False, + "pip_version": "latest", + "pip_legacy_resolver": False, + "pip_custom_flag": "--progress-bar on", "ui_source_map": True, }, ), diff --git a/tests/unit/testdata/openapi.json.multiple_account.generated b/tests/unit/testdata/openapi.json.multiple_account.generated index eb5d5c76a..d8a2a5b88 100644 --- a/tests/unit/testdata/openapi.json.multiple_account.generated +++ b/tests/unit/testdata/openapi.json.multiple_account.generated @@ -65,7 +65,11 @@ "type": "string" }, "auth_type": { - "type": "string" + "type": "string", + "enum": [ + "basic", + "oauth" + ] }, "username": { "type": "string" @@ -80,6 +84,25 @@ }, "basic_oauth_text": { "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, + "endpoint_token": { + "type": "string" + }, + "endpoint_authorize": { + "type": "string" + }, + "oauth_oauth_text": { + "type": "string" } } }, @@ -116,7 +139,11 @@ "type": "string" }, "auth_type": { - "type": "string" + "type": "string", + "enum": [ + "basic", + "oauth" + ] }, "username": { "type": "string" @@ -131,6 +158,25 @@ }, "basic_oauth_text": { "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, + "endpoint_token": { + "type": "string" + }, + "endpoint_authorize": { + "type": "string" + }, + "oauth_oauth_text": { + "type": "string" } } }, diff --git a/tests/unit/testdata/openapi.json.single_authentication.generated b/tests/unit/testdata/openapi.json.single_authentication.generated new file mode 100644 index 000000000..048f325a0 --- /dev/null +++ b/tests/unit/testdata/openapi.json.single_authentication.generated @@ -0,0 +1,3399 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Splunk_TA_UCCExample", + "version": "5.52.0+443c82a36", + "description": "Splunk UCC test Add-on", + "contact": { + "name": "Splunk", + "email": "addonfactory@splunk.com" + }, + "license": { + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0" + } + }, + "servers": [ + { + "url": "https://{domain}:{port}/servicesNS/-/Splunk_TA_UCCExample", + "variables": { + "domain": { + "default": "localhost" + }, + "port": { + "default": "8089" + } + }, + "description": "Access via management interface" + } + ], + "components": { + "schemas": { + "account": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "field_no_validators": { + "type": "string" + }, + "field_no_validators_suppressed": { + "type": "string" + }, + "custom_endpoint": { + "type": "string", + "enum": [ + "login.example.com", + "test.example.com", + "other" + ] + }, + "endpoint": { + "type": "string" + }, + "text_field_hidden_for_cloud": { + "type": "string" + }, + "text_field_hidden_for_enterprise": { + "type": "string" + }, + "url": { + "type": "string" + }, + "account_checkbox": { + "type": "string" + }, + "account_radio": { + "type": "string" + }, + "account_multiple_select": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, + "endpoint_token": { + "type": "string" + }, + "endpoint_authorize": { + "type": "string" + }, + "oauth_oauth_text": { + "type": "string" + } + } + }, + "account_without_name": { + "type": "object", + "properties": { + "field_no_validators": { + "type": "string" + }, + "field_no_validators_suppressed": { + "type": "string" + }, + "custom_endpoint": { + "type": "string", + "enum": [ + "login.example.com", + "test.example.com", + "other" + ] + }, + "endpoint": { + "type": "string" + }, + "text_field_hidden_for_cloud": { + "type": "string" + }, + "text_field_hidden_for_enterprise": { + "type": "string" + }, + "url": { + "type": "string" + }, + "account_checkbox": { + "type": "string" + }, + "account_radio": { + "type": "string" + }, + "account_multiple_select": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, + "endpoint_token": { + "type": "string" + }, + "endpoint_authorize": { + "type": "string" + }, + "oauth_oauth_text": { + "type": "string" + } + } + }, + "proxy": { + "type": "object", + "properties": { + "proxy_enabled": { + "type": "string" + }, + "proxy_type": { + "type": "string", + "enum": [ + "http", + "socks4", + "socks5" + ] + }, + "proxy_url": { + "type": "string" + }, + "proxy_port": { + "type": "string" + }, + "proxy_username": { + "type": "string" + }, + "proxy_password": { + "type": "string", + "format": "password" + }, + "proxy_rdns": { + "type": "string" + } + } + }, + "proxy_without_name": { + "type": "object", + "properties": { + "proxy_enabled": { + "type": "string" + }, + "proxy_type": { + "type": "string", + "enum": [ + "http", + "socks4", + "socks5" + ] + }, + "proxy_url": { + "type": "string" + }, + "proxy_port": { + "type": "string" + }, + "proxy_username": { + "type": "string" + }, + "proxy_password": { + "type": "string", + "format": "password" + }, + "proxy_rdns": { + "type": "string" + } + } + }, + "logging": { + "type": "object", + "properties": { + "loglevel": { + "type": "string", + "enum": [ + "DEBUG", + "INFO", + "WARNING", + "ERROR", + "CRITICAL" + ] + } + } + }, + "logging_without_name": { + "type": "object", + "properties": { + "loglevel": { + "type": "string", + "enum": [ + "DEBUG", + "INFO", + "WARNING", + "ERROR", + "CRITICAL" + ] + } + } + }, + "custom_abc": { + "type": "object", + "properties": { + "testString": { + "type": "string" + }, + "testNumber": { + "type": "string" + }, + "testRegex": { + "type": "string" + }, + "testEmail": { + "type": "string" + }, + "testIpv4": { + "type": "string" + }, + "testDate": { + "type": "string" + }, + "testUrl": { + "type": "string" + } + } + }, + "custom_abc_without_name": { + "type": "object", + "properties": { + "testString": { + "type": "string" + }, + "testNumber": { + "type": "string" + }, + "testRegex": { + "type": "string" + }, + "testEmail": { + "type": "string" + }, + "testIpv4": { + "type": "string" + }, + "testDate": { + "type": "string" + }, + "testUrl": { + "type": "string" + } + } + }, + "custom_row_tab": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "text_test": { + "type": "string" + } + } + }, + "custom_row_tab_without_name": { + "type": "object", + "properties": { + "text_test": { + "type": "string" + } + } + }, + "tab_hidden_for_cloud": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "text_test": { + "type": "string" + } + } + }, + "tab_hidden_for_cloud_without_name": { + "type": "object", + "properties": { + "text_test": { + "type": "string" + } + } + }, + "tab_hidden_for_enterprise": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "text_test": { + "type": "string" + } + } + }, + "tab_hidden_for_enterprise_without_name": { + "type": "object", + "properties": { + "text_test": { + "type": "string" + } + } + }, + "example_input_one": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "input_one_checkbox": { + "type": "string" + }, + "input_one_radio": { + "type": "string" + }, + "singleSelectTest": { + "type": "string", + "enum": [ + "one", + "two", + "three", + "four" + ] + }, + "multipleSelectTest": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "index": { + "type": "string" + }, + "account": { + "type": "string" + }, + "object": { + "type": "string" + }, + "object_fields": { + "type": "string" + }, + "order_by": { + "type": "string" + }, + "use_existing_checkpoint": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "limit": { + "type": "string" + }, + "example_textarea_field": { + "type": "string" + }, + "hide_in_ui": { + "type": "string" + }, + "hard_disabled": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_one_without_name": { + "type": "object", + "properties": { + "input_one_checkbox": { + "type": "string" + }, + "input_one_radio": { + "type": "string" + }, + "singleSelectTest": { + "type": "string", + "enum": [ + "one", + "two", + "three", + "four" + ] + }, + "multipleSelectTest": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "index": { + "type": "string" + }, + "account": { + "type": "string" + }, + "object": { + "type": "string" + }, + "object_fields": { + "type": "string" + }, + "order_by": { + "type": "string" + }, + "use_existing_checkpoint": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "limit": { + "type": "string" + }, + "example_textarea_field": { + "type": "string" + }, + "hide_in_ui": { + "type": "string" + }, + "hard_disabled": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_one_without_disabled": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "input_one_checkbox": { + "type": "string" + }, + "input_one_radio": { + "type": "string" + }, + "singleSelectTest": { + "type": "string", + "enum": [ + "one", + "two", + "three", + "four" + ] + }, + "multipleSelectTest": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "index": { + "type": "string" + }, + "account": { + "type": "string" + }, + "object": { + "type": "string" + }, + "object_fields": { + "type": "string" + }, + "order_by": { + "type": "string" + }, + "use_existing_checkpoint": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "limit": { + "type": "string" + }, + "example_textarea_field": { + "type": "string" + }, + "hide_in_ui": { + "type": "string" + }, + "hard_disabled": { + "type": "string" + } + } + }, + "example_input_two": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "index": { + "type": "string" + }, + "account": { + "type": "string" + }, + "input_two_text_hidden_for_cloud": { + "type": "string" + }, + "input_two_text_hidden_for_enterprise": { + "type": "string" + }, + "input_two_multiple_select": { + "type": "string" + }, + "input_two_checkbox": { + "type": "string" + }, + "input_two_radio": { + "type": "string" + }, + "use_existing_checkpoint": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "apis": { + "type": "string" + }, + "hide_in_ui": { + "type": "string" + }, + "hard_disabled": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_two_without_name": { + "type": "object", + "properties": { + "interval": { + "type": "string" + }, + "index": { + "type": "string" + }, + "account": { + "type": "string" + }, + "input_two_text_hidden_for_cloud": { + "type": "string" + }, + "input_two_text_hidden_for_enterprise": { + "type": "string" + }, + "input_two_multiple_select": { + "type": "string" + }, + "input_two_checkbox": { + "type": "string" + }, + "input_two_radio": { + "type": "string" + }, + "use_existing_checkpoint": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "apis": { + "type": "string" + }, + "hide_in_ui": { + "type": "string" + }, + "hard_disabled": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_two_without_disabled": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "index": { + "type": "string" + }, + "account": { + "type": "string" + }, + "input_two_text_hidden_for_cloud": { + "type": "string" + }, + "input_two_text_hidden_for_enterprise": { + "type": "string" + }, + "input_two_multiple_select": { + "type": "string" + }, + "input_two_checkbox": { + "type": "string" + }, + "input_two_radio": { + "type": "string" + }, + "use_existing_checkpoint": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "apis": { + "type": "string" + }, + "hide_in_ui": { + "type": "string" + }, + "hard_disabled": { + "type": "string" + } + } + }, + "example_input_three": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_three_without_name": { + "type": "object", + "properties": { + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_three_without_disabled": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + } + } + }, + "example_input_four": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_four_without_name": { + "type": "object", + "properties": { + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "example_input_four_without_disabled": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + } + } + }, + "service_hidden_for_cloud": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "service_hidden_for_cloud_without_name": { + "type": "object", + "properties": { + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "service_hidden_for_cloud_without_disabled": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + } + } + }, + "service_hidden_for_enterprise": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "service_hidden_for_enterprise_without_name": { + "type": "object", + "properties": { + "interval": { + "type": "string" + }, + "disabled": { + "type": "string", + "enum": [ + "False", + "True" + ] + } + } + }, + "service_hidden_for_enterprise_without_disabled": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + } + } + } + }, + "securitySchemes": { + "BasicAuth": { + "type": "http", + "scheme": "basic" + } + } + }, + "paths": { + "/splunk_ta_uccexample_account": { + "get": { + "responses": { + "200": { + "description": "Get list of items for account", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/account_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for account", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in account", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/account_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in account", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/account" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_account/{name}": { + "get": { + "responses": { + "200": { + "description": "Get account item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/account_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get account item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update account item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/account_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update account item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/account_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete account item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/account_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete account item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_settings/proxy": { + "get": { + "responses": { + "200": { + "description": "Get list of items for proxy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/proxy_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for proxy", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in proxy", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/proxy_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in proxy", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/proxy" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_settings/logging": { + "get": { + "responses": { + "200": { + "description": "Get list of items for logging", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/logging_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for logging", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in logging", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/logging_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in logging", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/logging" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_settings/custom_abc": { + "get": { + "responses": { + "200": { + "description": "Get list of items for custom_abc", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/custom_abc_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for custom_abc", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in custom_abc", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/custom_abc_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in custom_abc", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/custom_abc" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_custom_row_tab": { + "get": { + "responses": { + "200": { + "description": "Get list of items for custom_row_tab", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/custom_row_tab_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for custom_row_tab", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in custom_row_tab", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/custom_row_tab_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in custom_row_tab", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/custom_row_tab" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_custom_row_tab/{name}": { + "get": { + "responses": { + "200": { + "description": "Get custom_row_tab item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/custom_row_tab_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get custom_row_tab item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update custom_row_tab item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/custom_row_tab_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update custom_row_tab item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/custom_row_tab_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete custom_row_tab item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/custom_row_tab_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete custom_row_tab item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_tab_hidden_for_cloud": { + "get": { + "responses": { + "200": { + "description": "Get list of items for tab_hidden_for_cloud", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for tab_hidden_for_cloud", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in tab_hidden_for_cloud", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in tab_hidden_for_cloud", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/tab_hidden_for_cloud" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_tab_hidden_for_cloud/{name}": { + "get": { + "responses": { + "200": { + "description": "Get tab_hidden_for_cloud item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get tab_hidden_for_cloud item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update tab_hidden_for_cloud item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update tab_hidden_for_cloud item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/tab_hidden_for_cloud_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete tab_hidden_for_cloud item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete tab_hidden_for_cloud item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_tab_hidden_for_enterprise": { + "get": { + "responses": { + "200": { + "description": "Get list of items for tab_hidden_for_enterprise", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for tab_hidden_for_enterprise", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in tab_hidden_for_enterprise", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in tab_hidden_for_enterprise", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/tab_hidden_for_enterprise" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_tab_hidden_for_enterprise/{name}": { + "get": { + "responses": { + "200": { + "description": "Get tab_hidden_for_enterprise item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get tab_hidden_for_enterprise item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update tab_hidden_for_enterprise item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update tab_hidden_for_enterprise item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/tab_hidden_for_enterprise_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete tab_hidden_for_enterprise item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/tab_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete tab_hidden_for_enterprise item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_one": { + "get": { + "responses": { + "200": { + "description": "Get list of items for example_input_one", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_one_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for example_input_one", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in example_input_one", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_one_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in example_input_one", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_one_without_disabled" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_one/{name}": { + "get": { + "responses": { + "200": { + "description": "Get example_input_one item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_one_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get example_input_one item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update example_input_one item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_one_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update example_input_one item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_one_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete example_input_one item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_one_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete example_input_one item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_two": { + "get": { + "responses": { + "200": { + "description": "Get list of items for example_input_two", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_two_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for example_input_two", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in example_input_two", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_two_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in example_input_two", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_two_without_disabled" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_two/{name}": { + "get": { + "responses": { + "200": { + "description": "Get example_input_two item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_two_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get example_input_two item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update example_input_two item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_two_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update example_input_two item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_two_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete example_input_two item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_two_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete example_input_two item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_three": { + "get": { + "responses": { + "200": { + "description": "Get list of items for example_input_three", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_three_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for example_input_three", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in example_input_three", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_three_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in example_input_three", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_three_without_disabled" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_three/{name}": { + "get": { + "responses": { + "200": { + "description": "Get example_input_three item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_three_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get example_input_three item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update example_input_three item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_three_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update example_input_three item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_three_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete example_input_three item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_three_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete example_input_three item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_four": { + "get": { + "responses": { + "200": { + "description": "Get list of items for example_input_four", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_four_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for example_input_four", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in example_input_four", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_four_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in example_input_four", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_four_without_disabled" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_example_input_four/{name}": { + "get": { + "responses": { + "200": { + "description": "Get example_input_four item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_four_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get example_input_four item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update example_input_four item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_four_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update example_input_four item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/example_input_four_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete example_input_four item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/example_input_four_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete example_input_four item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_service_hidden_for_cloud": { + "get": { + "responses": { + "200": { + "description": "Get list of items for service_hidden_for_cloud", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for service_hidden_for_cloud", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in service_hidden_for_cloud", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in service_hidden_for_cloud", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/service_hidden_for_cloud_without_disabled" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_service_hidden_for_cloud/{name}": { + "get": { + "responses": { + "200": { + "description": "Get service_hidden_for_cloud item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get service_hidden_for_cloud item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update service_hidden_for_cloud item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update service_hidden_for_cloud item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/service_hidden_for_cloud_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete service_hidden_for_cloud item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_cloud_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete service_hidden_for_cloud item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_service_hidden_for_enterprise": { + "get": { + "responses": { + "200": { + "description": "Get list of items for service_hidden_for_enterprise", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get list of items for service_hidden_for_enterprise", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Create item in service_hidden_for_enterprise", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Create item in service_hidden_for_enterprise", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/service_hidden_for_enterprise_without_disabled" + } + } + }, + "required": false + }, + "deprecated": false + }, + "parameters": [ + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + }, + "/splunk_ta_uccexample_service_hidden_for_enterprise/{name}": { + "get": { + "responses": { + "200": { + "description": "Get service_hidden_for_enterprise item details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Get service_hidden_for_enterprise item details", + "deprecated": false + }, + "post": { + "responses": { + "200": { + "description": "Update service_hidden_for_enterprise item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Update service_hidden_for_enterprise item", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/service_hidden_for_enterprise_without_name" + } + } + }, + "required": false + }, + "deprecated": false + }, + "delete": { + "responses": { + "200": { + "description": "Delete service_hidden_for_enterprise item", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "content": { + "$ref": "#/components/schemas/service_hidden_for_enterprise_without_name" + } + } + } + } + } + } + } + } + } + }, + "description": "Delete service_hidden_for_enterprise item", + "deprecated": false + }, + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "description": "The name of the item to operate on", + "schema": { + "type": "string" + } + }, + { + "name": "output_mode", + "in": "query", + "required": true, + "description": "Output mode", + "schema": { + "type": "string", + "enum": [ + "json" + ], + "default": "json" + } + } + ] + } + }, + "security": [ + { + "BasicAuth": [] + } + ] +} \ No newline at end of file diff --git a/tests/unit/testdata/openapi.json.valid_config.generated b/tests/unit/testdata/openapi.json.valid_config.generated index 5df7314c0..1584f9ece 100644 --- a/tests/unit/testdata/openapi.json.valid_config.generated +++ b/tests/unit/testdata/openapi.json.valid_config.generated @@ -59,7 +59,11 @@ "type": "string" }, "auth_type": { - "type": "string" + "type": "string", + "enum": [ + "basic", + "oauth" + ] }, "username": { "type": "string" @@ -72,6 +76,16 @@ "type": "string", "format": "password" }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, "textarea_field": { "type": "string" }, @@ -108,7 +122,11 @@ "type": "string" }, "auth_type": { - "type": "string" + "type": "string", + "enum": [ + "basic", + "oauth" + ] }, "username": { "type": "string" @@ -121,6 +139,16 @@ "type": "string", "format": "password" }, + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string", + "format": "password" + }, + "redirect_url": { + "type": "string" + }, "textarea_field": { "type": "string" }, diff --git a/tests/unit/testdata/valid_config_input_with_enable_action.json b/tests/unit/testdata/valid_config_input_with_enable_action.json new file mode 100644 index 000000000..b0b1c1897 --- /dev/null +++ b/tests/unit/testdata/valid_config_input_with_enable_action.json @@ -0,0 +1,107 @@ +{ + "pages": { + "configuration": { + "tabs": [ + { + "type": "LoggingTab" + } + ], + "title": "Configuration", + "description": "Set up your add-on" + }, + "inputs": { + "services": [ + { + "name": "example_input_ten", + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], + "title": "Example Input Three" + } + ], + "title": "Inputs", + "description": "Manage your data inputs", + "table": { + "actions": [ + "edit", + "delete", + "clone", + "enable" + ], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Index", + "field": "index" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Index", + "field": "index" + }, + { + "label": "Status", + "field": "disabled", + "mapping": { + "true": "Disabled", + "false": "Enabled" + } + } + ] + } + } + }, + "meta": { + "name": "Splunk_TA_UCCExample", + "restRoot": "splunk_ta_uccexample", + "version": "1.0.0", + "displayName": "Splunk UCC test Add-on", + "schemaVersion": "0.0.8" + } +} \ No newline at end of file diff --git a/tests/unit/testdata/valid_single_authentication_config.json b/tests/unit/testdata/valid_single_authentication_config.json new file mode 100644 index 000000000..b072ef921 --- /dev/null +++ b/tests/unit/testdata/valid_single_authentication_config.json @@ -0,0 +1,1766 @@ +{ + "pages": { + "configuration": { + "tabs": [ + { + "name": "account", + "warning": { + "create": { + "message": "Some warning for account text create", + "alwaysDisplay": true + }, + "edit": { + "message": "Some warning for account text edit" + }, + "clone": { + "message": "Some warning for account text clone" + } + }, + "table": { + "actions": [ + "edit", + "delete", + "clone" + ], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Auth Type", + "field": "auth_type" + } + ] + }, + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "string", + "errorMsg": "Length of ID should be between 1 and 50", + "minLength": 1, + "maxLength": 50 + }, + { + "type": "regex", + "errorMsg": "Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + } + ], + "field": "name", + "help": "Enter a unique name for this account.", + "required": true + }, + { + "type": "text", + "label": "Field no validators", + "field": "field_no_validators", + "help": "Some field", + "required": false + }, + { + "type": "text", + "label": "Field no validators (suppressed)", + "field": "field_no_validators_suppressed", + "help": "Some field", + "required": false, + "validators": [] + }, + { + "type": "singleSelect", + "label": "Example Environment", + "options": { + "disableSearch": true, + "autoCompleteFields": [ + { + "value": "login.example.com", + "label": "Value1" + }, + { + "value": "test.example.com", + "label": "Value2" + }, + { + "value": "other", + "label": "Other" + } + ], + "display": true + }, + "help": "", + "field": "custom_endpoint", + "defaultValue": "login.example.com", + "required": true + }, + { + "type": "text", + "label": "Endpoint URL", + "help": "Enter the endpoint URL.", + "field": "endpoint", + "options": { + "display": false, + "requiredWhenVisible": true + } + }, + { + "type": "text", + "label": "Example text hidden for cloud", + "help": "Enter text for field hidden for cloud", + "field": "text_field_hidden_for_cloud", + "options": { + "hideForPlatform": "cloud" + } + }, + { + "type": "text", + "label": "Example text hidden for enterprise", + "help": "Enter text for field hidden for enterprise", + "field": "text_field_hidden_for_enterprise", + "options": { + "hideForPlatform": "enterprise" + } + }, + { + "field": "url", + "label": "URL", + "type": "text", + "help": "Enter the URL, for example", + "required": false, + "validators": [ + { + "errorMsg": "Invalid URL provided. URL should start with 'https' as only secure URLs are supported. Provide URL in this format", + "type": "regex", + "pattern": "^(https://)[^/]+/?$" + } + ] + }, + { + "type": "checkbox", + "label": "Example Checkbox", + "field": "account_checkbox", + "help": "This is an example checkbox for the account entity" + }, + { + "type": "radio", + "label": "Example Radio", + "field": "account_radio", + "defaultValue": "yes", + "help": "This is an example radio button for the account entity", + "required": true, + "options": { + "items": [ + { + "value": "yes", + "label": "Yes" + }, + { + "value": "no", + "label": "No" + } + ], + "display": true + } + }, + { + "type": "multipleSelect", + "label": "Example Multiple Select", + "field": "account_multiple_select", + "help": "This is an example multipleSelect for account entity", + "required": true, + "options": { + "items": [ + { + "value": "one", + "label": "Option One" + }, + { + "value": "two", + "label": "Option Two" + } + ] + } + }, + { + "type": "oauth", + "field": "oauth", + "label": "Not used", + "options": { + "auth_type": [ + "oauth" + ], + "oauth": [ + { + "oauth_field": "client_id", + "label": "Client Id", + "field": "client_id", + "help": "Enter the Client Id for this account." + }, + { + "oauth_field": "client_secret", + "label": "Client Secret", + "field": "client_secret", + "encrypted": true, + "help": "Enter the Client Secret key for this account." + }, + { + "oauth_field": "redirect_url", + "label": "Redirect url", + "field": "redirect_url", + "help": "Copy and paste this URL into your app." + }, + { + "oauth_field": "endpoint_token", + "label": "Token endpoint", + "field": "endpoint_token", + "help": "Put here endpoint used for token acqusition ie. login.salesforce.com", + "modifyFieldsOnValue": [ + { + "fieldValue": "[[any_other_value]]", + "fieldsToModify": [ + { + "fieldId": "oauth_oauth_text", + "label": "Disabled on edit for oauth 55555" + } + ] + }, + { + "fieldValue": "aaaaaa", + "fieldsToModify": [ + { + "fieldId": "oauth_oauth_text", + "disabled": false, + "label": "Disabled on edit for oauth 1111" + }, + { + "fieldId": "endpoint_authorize", + "display": true + } + ] + }, + { + "fieldValue": "bbbbb", + "fieldsToModify": [ + { + "fieldId": "oauth_oauth_text", + "disabled": true, + "label": "Disabled on edit for oauth 2222" + }, + { + "fieldId": "endpoint_authorize", + "display": false + } + ] + }, + { + "fieldValue": "ccccc", + "mode": "edit", + "fieldsToModify": [ + { + "fieldId": "oauth_oauth_text", + "disabled": false, + "label": "Disabled on edit for oauth 3333" + }, + { + "fieldId": "endpoint_authorize", + "display": false + } + ] + }, + { + "fieldValue": "dddddd", + "mode": "create", + "fieldsToModify": [ + { + "fieldId": "oauth_oauth_text", + "disabled": true, + "label": "Disabled on edit for oauth 44444" + }, + { + "fieldId": "endpoint_authorize", + "display": false + } + ] + } + ] + }, + { + "oauth_field": "endpoint_authorize", + "label": "Authorize endpoint", + "field": "endpoint_authorize", + "help": "Put here endpoint used for authorization ie. login.salesforce.com" + }, + { + "oauth_field": "oauth_some_text", + "label": "Disabled on edit for oauth", + "help": "Enter text for field disabled on edit", + "field": "oauth_oauth_text", + "required": false, + "options": { + "disableonEdit": true, + "enable": false + } + } + ], + "auth_code_endpoint": "/services/oauth2/authorize", + "access_token_endpoint": "/services/oauth2/token", + "oauth_timeout": 30, + "oauth_state_enabled": false + } + }, + { + "field": "example_help_link", + "label": "", + "type": "helpLink", + "options": { + "text": "Help Link", + "link": "https://docs.splunk.com/Documentation" + } + }, + { + "field": "config1_help_link", + "type": "helpLink", + "options": { + "text": "Add-on configuration documentation", + "link": "https://docs.splunk.com/Documentation" + } + }, + { + "field": "config2_help_link", + "type": "helpLink", + "options": { + "text": "SSL configuration documentation", + "link": "https://docs.splunk.com/Documentation" + } + } + ], + "title": "Account", + "restHandlerModule": "splunk_ta_uccexample_validate_account_rh", + "restHandlerClass": "CustomAccountValidator" + }, + { + "name": "proxy", + "warning": { + "config": { + "message": "Some warning for account text config" + } + }, + "entity": [ + { + "type": "checkbox", + "label": "Enable", + "field": "proxy_enabled" + }, + { + "type": "singleSelect", + "label": "Proxy Type", + "options": { + "disableSearch": true, + "autoCompleteFields": [ + { + "value": "http", + "label": "http" + }, + { + "value": "socks4", + "label": "socks4" + }, + { + "value": "socks5", + "label": "socks5" + } + ] + }, + "defaultValue": "http", + "field": "proxy_type" + }, + { + "type": "text", + "label": "Host", + "validators": [ + { + "type": "string", + "errorMsg": "Max host length is 4096", + "minLength": 0, + "maxLength": 4096 + }, + { + "type": "regex", + "errorMsg": "Proxy Host should not have special characters", + "pattern": "^[a-zA-Z]\\w*$" + } + ], + "field": "proxy_url" + }, + { + "type": "text", + "label": "Port", + "validators": [ + { + "type": "number", + "range": [ + 1, + 65535 + ], + "isInteger": true + } + ], + "field": "proxy_port" + }, + { + "type": "text", + "label": "Username", + "validators": [ + { + "type": "string", + "errorMsg": "Max length of username is 50", + "minLength": 0, + "maxLength": 50 + } + ], + "field": "proxy_username" + }, + { + "type": "text", + "label": "Password", + "validators": [ + { + "type": "string", + "errorMsg": "Max length of password is 8192", + "minLength": 0, + "maxLength": 8192 + } + ], + "encrypted": true, + "field": "proxy_password" + }, + { + "type": "checkbox", + "label": "Reverse DNS resolution", + "field": "proxy_rdns" + } + ], + "options": { + "saveValidator": "function(formData) { if(!formData.proxy_enabled || formData.proxy_enabled === '0') {return true; } if(!formData.proxy_url) { return 'Proxy Host can not be empty'; } if(!formData.proxy_port) { return 'Proxy Port can not be empty'; } return true; }" + }, + "title": "Proxy" + }, + { + "type": "loggingTab" + }, + { + "name": "custom_abc", + "title": "Customized tab", + "entity": [ + { + "field": "testString", + "label": "Test String", + "type": "text", + "validators": [ + { + "type": "string", + "maxLength": 10, + "minLength": 5 + } + ] + }, + { + "field": "testNumber", + "label": "Test Number", + "type": "text", + "validators": [ + { + "type": "number", + "range": [ + 1, + 10 + ] + } + ] + }, + { + "field": "testRegex", + "label": "Test Regex", + "type": "text", + "validators": [ + { + "type": "regex", + "pattern": "^\\w+$", + "errorMsg": "Characters of Name should match regex ^\\w+$ ." + } + ] + }, + { + "field": "testEmail", + "label": "Test Email", + "type": "text", + "validators": [ + { + "type": "email" + } + ] + }, + { + "field": "testIpv4", + "label": "Test Ipv4", + "type": "text", + "validators": [ + { + "type": "ipv4" + } + ] + }, + { + "field": "testDate", + "label": "Test Date", + "type": "text", + "validators": [ + { + "type": "date" + } + ] + }, + { + "field": "testUrl", + "label": "Test Url", + "type": "text", + "validators": [ + { + "type": "url" + } + ] + } + ] + }, + { + "name": "custom_row_tab", + "table": { + "actions": [ + "edit", + "delete", + "clone" + ], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Text Test", + "field": "text_test" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + }, + { + "field": "text_test", + "label": "Text Test" + } + ], + "customRow": { + "src": "custom_input_row_replace", + "type": "external" + } + }, + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "string", + "errorMsg": "Length of ID should be between 1 and 50", + "minLength": 1, + "maxLength": 50 + }, + { + "type": "regex", + "errorMsg": "Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + } + ], + "field": "name", + "help": "Enter a unique name for this account.", + "required": true + }, + { + "field": "text_test", + "label": "Text Test", + "help": "This is a text test", + "type": "text", + "validators": [ + { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + ], + "required": true + } + ], + "title": "Custom table row" + }, + { + "name": "tab_hidden_for_cloud", + "table": { + "actions": [ + "edit", + "delete", + "clone" + ], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Text Test", + "field": "text_test" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + }, + { + "field": "text_test", + "label": "Text Test" + } + ] + }, + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "string", + "errorMsg": "Length of ID should be between 1 and 50", + "minLength": 1, + "maxLength": 50 + }, + { + "type": "regex", + "errorMsg": "Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + } + ], + "field": "name", + "help": "Enter a unique name for this account.", + "required": true + }, + { + "field": "text_test", + "label": "Text Test", + "help": "This is a text test", + "type": "text", + "validators": [ + { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + ], + "required": true + } + ], + "title": "Tab hidden for cloud", + "hideForPlatform": "cloud" + }, + { + "name": "tab_hidden_for_enterprise", + "table": { + "actions": [ + "edit", + "delete", + "clone" + ], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Text Test", + "field": "text_test" + } + ], + "moreInfo": [ + { + "field": "name", + "label": "Name" + }, + { + "field": "text_test", + "label": "Text Test" + } + ] + }, + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "string", + "errorMsg": "Length of ID should be between 1 and 50", + "minLength": 1, + "maxLength": 50 + }, + { + "type": "regex", + "errorMsg": "Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + } + ], + "field": "name", + "help": "Enter a unique name for this account.", + "required": true + }, + { + "field": "text_test", + "label": "Text Test", + "help": "This is a text test", + "type": "text", + "validators": [ + { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + ], + "required": true + } + ], + "title": "Tab hidden for enterprise", + "hideForPlatform": "enterprise" + } + ], + "title": "Configuration", + "description": "Set up your add-on", + "subDescription": { + "text": "Configuration page - Ingesting data from to Splunk Cloud? Have you tried the new Splunk Data Manager yet?\nData Manager makes AWS data ingestion simpler, more automated and centrally managed for you, while co-existing with AWS and/or Kinesis TAs.\nRead our [[blogPost]] to learn more about Data Manager and it's availability on your Splunk Cloud instance.", + "links": [ + { + "slug": "blogPost", + "link": "https://splk.it/31oy2b2", + "linkText": "blog post" + } + ] + } + }, + "inputs": { + "services": [ + { + "name": "example_input_one", + "inputHelperModule": "helper_one", + "warning": { + "create": { + "message": "Some warning for account text create" + }, + "edit": { + "message": "Some warning for account text edit" + }, + "clone": { + "message": "Some warning for account text clone" + }, + "delete": { + "message": "Some warning for account text delete" + } + }, + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "checkbox", + "label": "Example Checkbox", + "field": "input_one_checkbox", + "help": "This is an example checkbox for the input one entity", + "defaultValue": true + }, + { + "type": "radio", + "label": "Example Radio", + "field": "input_one_radio", + "defaultValue": "yes", + "help": "This is an example radio button for the input one entity", + "required": false, + "options": { + "items": [ + { + "value": "yes", + "label": "Yes" + }, + { + "value": "no", + "label": "No" + } + ], + "display": true + } + }, + { + "field": "singleSelectTest", + "label": "Single Select Group Test", + "type": "singleSelect", + "options": { + "createSearchChoice": true, + "autoCompleteFields": [ + { + "label": "Group1", + "children": [ + { + "value": "one", + "label": "One" + }, + { + "value": "two", + "label": "Two" + } + ] + }, + { + "label": "Group2", + "children": [ + { + "value": "three", + "label": "Three" + }, + { + "value": "four", + "label": "Four" + } + ] + } + ] + } + }, + { + "field": "multipleSelectTest", + "label": "Multiple Select Test", + "type": "multipleSelect", + "defaultValue": "a|b", + "options": { + "delimiter": "|", + "items": [ + { + "value": "a", + "label": "A" + }, + { + "value": "b", + "label": "B" + } + ] + } + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + }, + { + "type": "singleSelect", + "label": "Index", + "validators": [ + { + "type": "string", + "errorMsg": "Length of index name should be between 1 and 80.", + "minLength": 1, + "maxLength": 80 + } + ], + "defaultValue": "default", + "options": { + "endpointUrl": "data/indexes", + "denyList": "^_.*$", + "createSearchChoice": true + }, + "field": "index", + "required": true + }, + { + "type": "singleSelect", + "label": "Example Account", + "options": { + "referenceName": "account" + }, + "help": "", + "field": "account", + "required": true + }, + { + "type": "text", + "label": "Object", + "validators": [ + { + "type": "string", + "errorMsg": "Max length of text input is 8192", + "minLength": 0, + "maxLength": 8192 + } + ], + "field": "object", + "help": "The name of the object to query for.", + "required": true + }, + { + "type": "text", + "label": "Object Fields", + "validators": [ + { + "type": "string", + "errorMsg": "Max length of text input is 8192", + "minLength": 0, + "maxLength": 8192 + } + ], + "field": "object_fields", + "help": "Object fields from which to collect data. Delimit multiple fields using a comma.", + "required": true + }, + { + "type": "text", + "label": "Order By", + "validators": [ + { + "type": "string", + "errorMsg": "Max length of text input is 8192", + "minLength": 0, + "maxLength": 8192 + } + ], + "defaultValue": "LastModifiedDate", + "field": "order_by", + "help": "The datetime field by which to query results in ascending order for indexing.", + "required": true + }, + { + "type": "radio", + "label": "Use existing data input?", + "field": "use_existing_checkpoint", + "defaultValue": "yes", + "help": "Data input already exists. Select `No` if you want to reset the data collection.", + "required": false, + "options": { + "items": [ + { + "value": "yes", + "label": "Yes" + }, + { + "value": "no", + "label": "No" + } + ], + "display": false + } + }, + { + "type": "text", + "label": "Query Start Date", + "validators": [ + { + "type": "regex", + "errorMsg": "Invalid date and time format", + "pattern": "^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}z)?$" + } + ], + "field": "start_date", + "help": "The datetime after which to query and index records, in this format: \"YYYY-MM-DDThh:mm:ss.000z\".\nDefaults to 90 days earlier from now.", + "tooltip": "Changing this parameter may result in gaps or duplication in data collection.", + "required": false + }, + { + "type": "text", + "label": "Limit", + "validators": [ + { + "type": "string", + "errorMsg": "Max length of text input is 8192", + "minLength": 0, + "maxLength": 8192 + } + ], + "defaultValue": "1000", + "field": "limit", + "help": "The maximum number of results returned by the query.", + "required": false + }, + { + "type": "textarea", + "label": "Example Textarea Field", + "field": "example_textarea_field", + "help": "Help message", + "options": { + "rowsMin": 3, + "rowsMax": 15 + }, + "required": true + }, + { + "field": "example_help_link", + "label": "", + "type": "helpLink", + "options": { + "text": "Help Link", + "link": "https://docs.splunk.com/Documentation" + } + }, + { + "type": "checkbox", + "label": "Hide in UI boolean value", + "field": "hide_in_ui", + "options": { + "display": false + } + }, + { + "type": "checkbox", + "label": "Is input readonly?", + "field": "hard_disabled", + "options": { + "display": false + } + } + ], + "title": "Example Input One" + }, + { + "name": "example_input_two", + "inputHelperModule": "helper_two", + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + }, + { + "type": "index", + "label": "Index", + "field": "index", + "required": true + }, + { + "type": "singleSelect", + "label": "Example Account", + "options": { + "referenceName": "account" + }, + "help": "", + "field": "account", + "required": true + }, + { + "type": "text", + "label": "Text input hidden for cloud", + "field": "input_two_text_hidden_for_cloud", + "help": "Should be hidden for cloud", + "tooltip": "Should be hidden for cloud", + "required": false, + "options": { + "hideForPlatform": "cloud" + } + }, + { + "type": "text", + "label": "Text input hidden for enterprise", + "field": "input_two_text_hidden_for_enterprise", + "help": "Should be hidden for enterprise", + "tooltip": "Should be hidden for enterprise", + "required": false, + "options": { + "hideForPlatform": "enterprise" + } + }, + { + "type": "multipleSelect", + "label": "Example Multiple Select", + "field": "input_two_multiple_select", + "help": "This is an example multipleSelect for input two entity", + "required": true, + "options": { + "items": [ + { + "value": "one", + "label": "Option One" + }, + { + "value": "two", + "label": "Option Two" + } + ] + } + }, + { + "type": "checkbox", + "label": "Example Checkbox", + "field": "input_two_checkbox", + "help": "This is an example checkbox for the input two entity" + }, + { + "type": "radio", + "label": "Example Radio", + "field": "input_two_radio", + "help": "This is an example radio button for the input two entity", + "required": true, + "options": { + "items": [ + { + "value": "yes", + "label": "Yes" + }, + { + "value": "no", + "label": "No" + } + ], + "display": true + } + }, + { + "type": "radio", + "label": "Use existing data input?", + "field": "use_existing_checkpoint", + "defaultValue": "yes", + "help": "Data input already exists. Select `No` if you want to reset the data collection.", + "required": false, + "options": { + "items": [ + { + "value": "yes", + "label": "Yes" + }, + { + "value": "no", + "label": "No" + } + ], + "display": false + } + }, + { + "type": "text", + "label": "Query Start Date", + "validators": [ + { + "type": "regex", + "errorMsg": "Invalid date and time format", + "pattern": "^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}z)?$" + } + ], + "field": "start_date", + "help": "The date and time, in \"YYYY-MM-DDThh:mm:ss.000z\" format, after which to query and index records. \nThe default is 90 days before today.", + "tooltip": "Changing this parameter may result in gaps or duplication in data collection.", + "required": false + }, + { + "field": "example_help_link", + "label": "", + "type": "helpLink", + "options": { + "text": "Help Link", + "link": "https://docs.splunk.com/Documentation" + } + }, + { + "field": "apis", + "label": "APIs/Interval (in seconds)", + "type": "checkboxGroup", + "options": { + "groups": [ + { + "label": "EC2", + "options": { + "isExpandable": true + }, + "fields": [ + "ec2_volumes", + "ec2_instances", + "ec2_reserved_instances", + "ebs_snapshots", + "rds_instances", + "rds_reserved_instances", + "ec2_key_pairs", + "ec2_security_groups", + "ec2_images", + "ec2_addresses" + ] + }, + { + "label": "ELB", + "options": { + "isExpandable": true + }, + "fields": [ + "classic_load_balancers", + "application_load_balancers" + ] + }, + { + "label": "VPC", + "options": { + "isExpandable": true + }, + "fields": [ + "vpcs", + "vpc_network_acls", + "vpc_subnets" + ] + } + ], + "rows": [ + { + "field": "ec2_volumes", + "checkbox": { + "defaultValue": true + }, + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "ec2_instances", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "ec2_reserved_instances", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "ebs_snapshots", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "rds_instances", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "rds_reserved_instances", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "ec2_key_pairs", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "ec2_security_groups", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "ec2_images", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "ec2_addresses", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "classic_load_balancers", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "application_load_balancers", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "vpcs", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "vpc_network_acls", + "input": { + "defaultValue": 3600, + "required": true + } + }, + { + "field": "vpc_subnets", + "input": { + "defaultValue": 3600, + "required": true + } + } + ] + } + }, + { + "type": "checkbox", + "label": "Hide in UI boolean value", + "field": "hide_in_ui", + "options": { + "display": false + } + }, + { + "type": "checkbox", + "label": "Is input readonly?", + "field": "hard_disabled", + "options": { + "display": false + } + } + ], + "title": "Example Input Two" + }, + { + "name": "example_input_three", + "restHandlerName": "splunk_ta_uccexample_rh_three_custom", + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], + "title": "Example Input Three" + }, + { + "name": "example_input_four", + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], + "title": "Example Input Four" + }, + { + "name": "service_hidden_for_cloud", + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], + "title": "Service hidden for cloud", + "hideForPlatform": "cloud" + }, + { + "name": "service_hidden_for_enterprise", + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], + "title": "Service hidden for enterprise", + "hideForPlatform": "enterprise" + } + ], + "title": "Inputs", + "description": "Manage your data inputs", + "subDescription": { + "text": "Input page - Ingesting data from to Splunk Cloud? Have you tried the new Splunk Data Manager yet?\nData Manager makes AWS data ingestion simpler, more automated and centrally managed for you, while co-existing with AWS and/or Kinesis TAs.\nRead our [[blogPost]] to learn more about Data Manager and it's availability on your Splunk Cloud instance.", + "links": [ + { + "slug": "blogPost", + "link": "https://splk.it/31oy2b2", + "linkText": "blog post" + } + ] + }, + "table": { + "actions": [ + "edit", + "delete", + "clone", + "search" + ], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Account", + "field": "account" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Index", + "field": "index" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "customRow": { + "src": "custom_input_row", + "type": "external" + }, + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Index", + "field": "index" + }, + { + "label": "Status", + "field": "disabled", + "mapping": { + "true": "Inactive", + "false": "Active" + } + }, + { + "label": "Example Account", + "field": "account" + }, + { + "label": "Object", + "field": "object" + }, + { + "label": "Object Fields", + "field": "object_fields" + }, + { + "label": "Order By", + "field": "order_by" + }, + { + "label": "Query Start Date", + "field": "start_date" + }, + { + "label": "Limit", + "field": "limit" + } + ] + }, + "hideFieldId": "hide_in_ui", + "readonlyFieldId": "hard_disabled" + }, + "dashboard": { + "panels": [ + { + "name": "default" + } + ] + } + }, + "alerts": [ + { + "name": "test_alert", + "label": "Test Alert", + "description": "Description for test Alert Action", + "iconFileName": "test icon.png", + "adaptiveResponse": { + "task": [ + "Create", + "Update" + ], + "supportsAdhoc": true, + "supportsCloud": true, + "subject": [ + "endpoint" + ], + "category": [ + "Information Conveyance", + "Information Portrayal" + ], + "technology": [ + { + "version": [ + "1.0.0" + ], + "product": "Test Incident Update", + "vendor": "Splunk" + } + ], + "drilldownUri": "search?q=search%20index%3D\"_internal\"&earliest=0&latest=", + "sourcetype": "test:incident" + }, + "entity": [ + { + "type": "text", + "label": "Name", + "field": "name", + "defaultValue": "xyz", + "required": true, + "help": "Please enter your name" + }, + { + "type": "textarea", + "label": "Description", + "field": "description", + "defaultValue": "some sample description", + "required": true, + "help": "Please enter the description for the alert" + }, + { + "type": "checkbox", + "label": "All Incidents", + "field": "all_incidents", + "defaultValue": 0, + "required": false, + "help": "Tick if you want to update all incidents/problems" + }, + { + "type": "singleSelect", + "label": "Table List", + "field": "table_list", + "options": { + "items": [ + { + "value": "incident", + "label": "Incident" + }, + { + "value": "problem", + "label": "Problem" + } + ] + }, + "help": "Please select the table", + "required": false, + "defaultValue": "problem" + }, + { + "type": "radio", + "label": "Action:", + "field": "action", + "options": { + "items": [ + { + "value": "update", + "label": "Update" + }, + { + "value": "delete", + "label": "Delete" + } + ] + }, + "help": "Select the action you want to perform", + "required": true, + "defaultValue": "update" + }, + { + "type": "singleSelectSplunkSearch", + "label": "Select Account", + "field": "account", + "search": "| rest /servicesNS/nobody/Splunk_TA_UCCExample/splunk_ta_uccexample_account | dedup title", + "options": { + "items": [ + { + "label": "earliest", + "value": "-4@h" + }, + { + "label": "latest", + "value": "now" + } + ] + }, + "valueField": "title", + "labelField": "title", + "help": "Select the account from the dropdown", + "required": true + } + ], + "customScript": "myAlertLogic" + } + ], + "meta": { + "name": "Splunk_TA_UCCExample", + "restRoot": "splunk_ta_uccexample", + "version": "5.52.0+443c82a36", + "displayName": "Splunk UCC test Add-on", + "schemaVersion": "0.0.9", + "_uccVersion": "5.52.0", + "supportedThemes": [ + "light", + "dark" + ], + "isVisible": true + } +} diff --git a/ui/README.md b/ui/README.md index 0748b74cd..5c4817fd9 100644 --- a/ui/README.md +++ b/ui/README.md @@ -9,8 +9,8 @@ For a quickstart, please check [quick_start_ui.sh](../scripts/quick_start_ui.sh) ## Getting Started 1. Clone the repo. -2. Install yarn (>= 1.2) if you haven't already: `npm install --global yarn`. -3. Run the setup task: `yarn run setup`. +1. Install yarn (>= 1.2) if you haven't already: `npm install --global yarn`. +1. Run the setup task: `yarn run setup`. After completing these steps, the following tasks will be available: @@ -82,7 +82,7 @@ Due to differences in application appearance between MacOS and Linux (especially yarn run storybook ``` -2. Capture and Update Screenshots: +1. Capture and Update Screenshots: Once Storybook is running, use Docker Compose to update the screenshots: diff --git a/ui/jest.config.ts b/ui/jest.config.ts index 0d4510a2d..2d87313a5 100644 --- a/ui/jest.config.ts +++ b/ui/jest.config.ts @@ -15,18 +15,23 @@ export default { coveragePathIgnorePatterns: [ '/node_modules/', '/stories/', - // ignore *.d.ts files - '\\.d\\.ts$', 'mockServiceWorker.js', 'styleMock.js', + /* + TYPES + */ + // *.d.ts files + '\\.d\\.ts$', + '/types/', + '\\.types\\.ts$', ], coverageDirectory: 'coverage', coverageThreshold: { global: { - statements: 65, - branches: 56, - functions: 64, - lines: 65, + statements: 66, + branches: 57, + functions: 65, + lines: 66, }, }, testEnvironmentOptions: { diff --git a/ui/jest.polyfills.ts b/ui/jest.polyfills.ts index 6fadf302e..94ad52b31 100644 --- a/ui/jest.polyfills.ts +++ b/ui/jest.polyfills.ts @@ -10,10 +10,14 @@ */ const { TextDecoder, TextEncoder } = require('node:util'); +const { TransformStream } = require('node:stream/web'); +const { BroadcastChannel } = require('node:worker_threads'); Object.defineProperties(globalThis, { TextDecoder: { value: TextDecoder }, TextEncoder: { value: TextEncoder }, + TransformStream: { value: TransformStream }, + BroadcastChannel: { value: BroadcastChannel }, }); const { Blob } = require('node:buffer'); @@ -24,6 +28,7 @@ Object.defineProperties(globalThis, { Blob: { value: Blob }, Headers: { value: Headers }, FormData: { value: FormData }, + BroadcastChannel: { value: BroadcastChannel }, Request: { value: Request, configurable: true }, Response: { value: Response, configurable: true }, }); diff --git a/ui/package.json b/ui/package.json index fffea826f..536bdd914 100644 --- a/ui/package.json +++ b/ui/package.json @@ -21,23 +21,22 @@ "test-storybook:update-snapshots": "yarn run test-storybook -u" }, "dependencies": { - "@splunk/dashboard-action-buttons": "^27.4.0", - "@splunk/dashboard-context": "^27.4.0", - "@splunk/dashboard-core": "^27.4.0", - "@splunk/dashboard-presets": "^27.4.0", - "@splunk/dashboard-state": "^27.4.0", - "@splunk/dashboard-types": "^27.4.0", + "@splunk/dashboard-action-buttons": "^27.5.0", + "@splunk/dashboard-context": "^27.5.0", + "@splunk/dashboard-core": "^27.5.0", + "@splunk/dashboard-presets": "^27.5.0", + "@splunk/dashboard-state": "^27.5.0", + "@splunk/dashboard-types": "^27.5.0", "@splunk/react-icons": "^4.8.0", "@splunk/react-page": "^7.1.0", "@splunk/react-toast-notifications": "^0.11.3", - "@splunk/react-ui": "^4.38.0", + "@splunk/react-ui": "^4.39.0", "@splunk/search-job": "^3.1.0", "@splunk/splunk-utils": "^3.1.0", "@splunk/themes": "^0.22.0", "@splunk/ui-utils": "^1.7.1", - "@splunk/visualization-context": "^26.3.0", - "@storybook/test": "^8.3.5", - "axios": "^1.7.7", + "@splunk/visualization-context": "^26.4.1", + "@storybook/test": "^8.4.2", "immutability-helper": "^3.1.1", "license-webpack-plugin": "^4.0.2", "lodash": "^4.17.21", @@ -45,38 +44,38 @@ "react": "^16.14.0", "react-dom": "^16.14.0", "react-is": "^16.13.1", - "react-router-dom": "^6.27.0", + "react-router-dom": "6.27.0", "styled-components": "^5.3.11", - "uuid": "^11.0.1", + "uuid": "^11.0.2", "zod": "^3.23.8" }, "devDependencies": { - "@babel/core": "^7.25.8", - "@babel/eslint-parser": "^7.25.8", - "@babel/plugin-transform-runtime": "^7.25.7", - "@babel/preset-env": "^7.25.8", - "@babel/preset-react": "^7.25.7", - "@kickstartds/core": "^4.0.5", + "@babel/core": "^7.26.0", + "@babel/eslint-parser": "^7.25.9", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.26.0", + "@babel/preset-react": "^7.25.9", + "@kickstartds/core": "^4.1.0", "@kickstartds/storybook-addon-jsonschema": "^4.0.0", "@splunk/babel-preset": "^4.0.0", "@splunk/eslint-config": "^4.0.0", "@splunk/webpack-configs": "^7.0.2", - "@storybook/addon-a11y": "^8.3.5", - "@storybook/addon-essentials": "^8.3.5", - "@storybook/addon-interactions": "^8.3.5", - "@storybook/addon-links": "^8.3.5", + "@storybook/addon-a11y": "^8.4.2", + "@storybook/addon-essentials": "^8.4.2", + "@storybook/addon-interactions": "^8.4.2", + "@storybook/addon-links": "^8.4.2", "@storybook/addon-webpack5-compiler-babel": "^3.0.3", - "@storybook/blocks": "^8.3.5", - "@storybook/react": "^8.3.5", - "@storybook/react-webpack5": "^8.3.5", + "@storybook/blocks": "^8.4.2", + "@storybook/react": "^8.4.2", + "@storybook/react-webpack5": "^8.4.2", "@storybook/test-runner": "^0.19.1", "@testing-library/dom": "^8.20.1", - "@testing-library/jest-dom": "^6.5.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^12.1.5", "@testing-library/user-event": "^14.5.2", - "@types/jest": "^29.5.13", + "@types/jest": "^29.5.14", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.16.11", + "@types/node": "^20.17.6", "@types/react": "^16.14.62", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -90,29 +89,29 @@ "eslint-config-prettier": "^7.2.0", "eslint-import-resolver-typescript": "^3.6.3", "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jest": "^28.8.3", - "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-jest": "^28.9.0", + "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.37.1", + "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-storybook": "^0.9.0", + "eslint-plugin-storybook": "^0.11.0", "fork-ts-checker-webpack-plugin": "^9.0.2", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-image-snapshot": "^6.4.0", "js-yaml": "^4.1.0", - "msw": "2.4.11", - "msw-storybook-addon": "^2.0.3", + "msw": "2.6.2", + "msw-storybook-addon": "^2.0.4", "prettier": "^2.8.8", "querystring-es3": "^0.2.1", - "storybook": "^8.3.5", + "storybook": "^8.4.2", "style-loader": "^4.0.0", "stylelint": "^14.16.1", "ts-node": "^10.9.2", "typescript": "^5.6.3", "undici": "^5.28.4", "url": "^0.11.4", - "webpack": "^5.95.0", + "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.1.0", "webpack-merge": "^6.0.1" diff --git a/ui/src/components/BaseFormView/BaseFormConfigMock.ts b/ui/src/components/BaseFormView/BaseFormConfigMock.ts index c39571777..7571fbdbb 100644 --- a/ui/src/components/BaseFormView/BaseFormConfigMock.ts +++ b/ui/src/components/BaseFormView/BaseFormConfigMock.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; -import { GlobalConfigSchema } from '../../types/globalConfig/globalConfig'; +import { GlobalConfig, GlobalConfigSchema } from '../../types/globalConfig/globalConfig'; +import { AnyOfEntity } from '../../types/globalConfig/entities'; const globalConfigMockCustomControl = { pages: { @@ -174,3 +175,234 @@ const globalConfigMockCustomControl = { export function getGlobalConfigMockCustomControl() { return GlobalConfigSchema.parse(globalConfigMockCustomControl); } + +const EXAMPLE_GROUPS_ENTITIES = [ + { + type: 'text', + label: 'Text 1 Group 2', + field: 'text_field_1_group_2', + required: false, + }, + { + type: 'text', + label: 'Text 2 Group 2', + field: 'text_field_2_group_2', + required: false, + }, + { + type: 'text', + label: 'Text 1 Group 1', + field: 'text_field_1_group_1', + required: false, + }, + { + type: 'text', + label: 'Text 2 Group 1', + field: 'text_field_2_group_1', + required: false, + }, + { + type: 'text', + label: 'Text 1 Group 3', + field: 'text_field_1_group_3', + required: false, + }, + { + type: 'text', + label: 'Text 2 Group 3', + field: 'text_field_2_group_3', + required: false, + }, +] satisfies z.input[]; + +const GROUPS_FOR_EXAMPLE_ENTITIES = [ + { + label: 'Group 1', + fields: ['text_field_1_group_1', 'text_field_2_group_1'], + }, + { + label: 'Group 2', + fields: ['text_field_1_group_2', 'text_field_2_group_2'], + options: { + isExpandable: true, + expand: true, + }, + }, + { + label: 'Group 3', + fields: ['text_field_1_group_3', 'text_field_2_group_3'], + options: { + isExpandable: true, + expand: false, + }, + }, +]; + +const globalConfigMockGroupsForConfigPage = { + pages: { + configuration: { + tabs: [ + { + name: 'account', + table: { + actions: ['edit', 'delete', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + entity: [ + { + type: 'text', + label: 'Name', + validators: [ + { + type: 'regex', + errorMsg: + 'Account Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.', + pattern: '^[a-zA-Z]\\w*$', + }, + ], + field: 'name', + help: 'A unique name for the account.', + required: true, + }, + ...EXAMPLE_GROUPS_ENTITIES, + ], + groups: GROUPS_FOR_EXAMPLE_ENTITIES, + title: 'Accounts', + }, + ], + title: 'Configuration', + description: 'Set up your add-on', + }, + inputs: { + services: [], + title: 'Inputs', + description: 'Manage your data inputs', + table: { + actions: ['edit', 'delete', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + }, + }, + meta: { + name: 'demo_addon_for_splunk', + restRoot: 'demo_addon_for_splunk', + version: '5.31.1R85f0e18e', + displayName: 'Demo Add-on for Splunk', + schemaVersion: '0.0.3', + checkForUpdates: false, + searchViewDefault: false, + }, +} satisfies z.input; + +export function getGlobalConfigMockGroupsForConfigPage(): GlobalConfig { + return GlobalConfigSchema.parse(globalConfigMockGroupsForConfigPage); +} + +const globalConfigMockGroupsForInputPage = { + pages: { + configuration: { + tabs: [ + { + name: 'account', + table: { + actions: ['edit', 'delete', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + entity: [ + { + type: 'text', + label: 'Name', + validators: [ + { + type: 'regex', + errorMsg: + 'Account Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.', + pattern: '^[a-zA-Z]\\w*$', + }, + ], + field: 'name', + help: 'A unique name for the account.', + required: true, + }, + ], + title: 'Accounts', + }, + ], + title: 'Configuration', + description: 'Set up your add-on', + }, + inputs: { + services: [ + { + name: 'demo_input', + entity: [ + { + type: 'text', + label: 'Name', + validators: [ + { + type: 'regex', + errorMsg: + 'Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.', + pattern: '^[a-zA-Z]\\w*$', + }, + { + type: 'string', + errorMsg: 'Length of input name should be between 1 and 100', + minLength: 1, + maxLength: 100, + }, + ], + field: 'name', + help: 'A unique name for the data input.', + required: true, + encrypted: false, + }, + ...EXAMPLE_GROUPS_ENTITIES, + ], + groups: GROUPS_FOR_EXAMPLE_ENTITIES, + title: 'demo_input', + }, + ], + title: 'Inputs', + description: 'Manage your data inputs', + table: { + actions: ['edit', 'delete', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + }, + }, + meta: { + name: 'demo_addon_for_splunk', + restRoot: 'demo_addon_for_splunk', + version: '5.31.1R85f0e18e', + displayName: 'Demo Add-on for Splunk', + schemaVersion: '0.0.3', + checkForUpdates: false, + searchViewDefault: false, + }, +} satisfies z.input; + +export function getGlobalConfigMockGroupsFoInputPage(): GlobalConfig { + return GlobalConfigSchema.parse(globalConfigMockGroupsForInputPage); +} diff --git a/ui/src/components/BaseFormView/BaseFormView.test.tsx b/ui/src/components/BaseFormView/BaseFormView.test.tsx index 3c69c0c88..ac05b2ec3 100644 --- a/ui/src/components/BaseFormView/BaseFormView.test.tsx +++ b/ui/src/components/BaseFormView/BaseFormView.test.tsx @@ -1,11 +1,17 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; +import userEvent from '@testing-library/user-event'; + import { getGlobalConfigMock } from '../../mocks/globalConfigMock'; import { setUnifiedConfig } from '../../util/util'; import BaseFormView from './BaseFormView'; import { getBuildDirPath } from '../../util/script'; import mockCustomControlMockForTest from '../CustomControl/CustomControlMockForTest'; -import { getGlobalConfigMockCustomControl } from './BaseFormConfigMock'; +import { + getGlobalConfigMockCustomControl, + getGlobalConfigMockGroupsFoInputPage, + getGlobalConfigMockGroupsForConfigPage, +} from './BaseFormConfigMock'; const handleFormSubmit = jest.fn(); @@ -14,6 +20,22 @@ const SERVICE_NAME = 'account'; const STANZA_NAME = 'stanzaName'; const CUSTOM_MODULE = 'CustomControl'; +const getElementsByGroup = (group: string) => { + const firstField = screen.queryByText(`Text 1 Group ${group}`); + const secondField = screen.queryByText(`Text 2 Group ${group}`); + return { firstField, secondField }; +}; +const verifyDisplayedGroup = (group: string) => { + const { firstField, secondField } = getElementsByGroup(group); + expect(firstField).toBeInTheDocument(); + expect(secondField).toBeInTheDocument(); +}; +const verifyNotDisplayedElement = (group: string) => { + const { firstField, secondField } = getElementsByGroup(group); + expect(firstField).not.toBeInTheDocument(); + expect(secondField).not.toBeInTheDocument(); +}; + it('should render base form correctly with name and File fields', async () => { const mockConfig = getGlobalConfigMock(); setUnifiedConfig(mockConfig); @@ -66,3 +88,61 @@ it('should pass default values to custom component correctly', async () => { expect((customModal as HTMLSelectElement)?.value).toEqual('input_three'); }); + +it.each([ + { + page: 'configuration' as const, + config: getGlobalConfigMockGroupsForConfigPage(), + service: 'account', + }, + { + page: 'inputs' as const, + config: getGlobalConfigMockGroupsFoInputPage(), + service: 'demo_input', + }, +])('entities grouping for page works properly %s', async ({ config, page, service }) => { + setUnifiedConfig(config); + + render( + + ); + const group1Header = await screen.findByText('Group 1', { exact: true }); + + const group2Header = await screen.findByRole('button', { name: 'Group 2' }); + + const group3Header = await screen.findByRole('button', { name: 'Group 3' }); + + verifyDisplayedGroup('1'); + verifyDisplayedGroup('2'); + verifyNotDisplayedElement('3'); // group 3 is not expanded by default + + expect(group3Header).toHaveAttribute('aria-expanded', 'false'); + await userEvent.click(group3Header); + verifyDisplayedGroup('3'); + expect(group3Header).toHaveAttribute('aria-expanded', 'true'); + + await userEvent.click(group1Header); // does not change anything + verifyDisplayedGroup('1'); + + expect(group2Header).toHaveAttribute('aria-expanded', 'true'); + await userEvent.click(group2Header); + expect(group2Header).toHaveAttribute('aria-expanded', 'false'); + + /** + * verifying aria-expanded attribute as in tests + * child elements are not removed from the DOM + * they are removed in browser + * todo: verify behaviour + */ + await userEvent.click(group2Header); + verifyDisplayedGroup('1'); + verifyDisplayedGroup('2'); + verifyDisplayedGroup('3'); // after modifications all groups should be displayed +}); diff --git a/ui/src/components/BaseFormView/BaseFormView.tsx b/ui/src/components/BaseFormView/BaseFormView.tsx index 48ec38401..b5189c01e 100644 --- a/ui/src/components/BaseFormView/BaseFormView.tsx +++ b/ui/src/components/BaseFormView/BaseFormView.tsx @@ -9,7 +9,7 @@ import Validator, { SaveValidator } from '../../util/Validator'; import { getUnifiedConfigs, generateToast } from '../../util/util'; import { MODE_CLONE, MODE_CREATE, MODE_EDIT, MODE_CONFIG } from '../../constants/modes'; import { PAGE_INPUT, PAGE_CONF } from '../../constants/pages'; -import { axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper'; +import { generateEndPointUrl, postRequest } from '../../util/api'; import { parseErrorMsg, getFormattedMessage } from '../../util/messageUtil'; import { getBuildDirPath } from '../../util/script'; @@ -44,7 +44,7 @@ import { ChangeRecord, CustomHookClass, EntitiesAllowingModifications, -} from './BaseFormTypes'; +} from '../../types/components/BaseFormTypes'; import { getAllFieldsWithModifications, getModifiedState, @@ -201,6 +201,8 @@ class BaseFormView extends PureComponent { : tab.name === props.stanzaName && props.serviceName === 'settings'; if (flag) { + this.groups = tab.groups; + this.updateGroupEntities(); this.entities = tab.entity; this.options = tab.options; if (props.mode !== 'delete') { @@ -863,15 +865,14 @@ class BaseFormView extends PureComponent { body.delete('name'); } - axiosCallWrapper({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + postRequest<{ entry: [any] }>({ endpointUrl: generateEndPointUrl(this.endpoint), body, - customHeaders: { 'Content-Type': 'application/x-www-form-urlencoded' }, - method: 'post', handleError: false, }) - .then((response) => { - const val = response?.data?.entry[0]; + .then((data) => { + const val = data?.entry[0]; if (this.props.mode !== MODE_CONFIG) { const tmpObj: Record> = {}; @@ -1119,7 +1120,7 @@ class BaseFormView extends PureComponent { * using rest call once oauth code received from child window */ // eslint-disable-next-line consistent-return - handleOauthToken = (message: { code: string; error: unknown; state: unknown }) => { + handleOauthToken = (message: { code: string; error: unknown; state?: string }) => { // Check message for error. If error show error message. if (!message || (message && message.error) || message.code === undefined) { this.setErrorMsg(ERROR_OCCURRED_TRY_AGAIN); @@ -1167,18 +1168,17 @@ class BaseFormView extends PureComponent { const OAuthEndpoint = `${encodeURIComponent(this.appName)}_oauth/oauth`; // Internal handler call to get the access token and other values - axiosCallWrapper({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + postRequest<{ entry: [{ content: any }] }>({ endpointUrl: OAuthEndpoint, body, - customHeaders: { 'Content-Type': 'application/x-www-form-urlencoded' }, - method: 'post', handleError: false, }) - .then((response) => { - if (response.data.entry[0].content.error === undefined) { - const accessToken = response.data.entry[0].content.access_token; - const instanceUrl = response.data.entry[0].content.instance_url; - const refreshToken = response.data.entry[0].content.refresh_token; + .then((responseData) => { + if (responseData.entry[0].content.error === undefined) { + const accessToken = responseData.entry[0].content.access_token; + const instanceUrl = responseData.entry[0].content.instance_url; + const refreshToken = responseData.entry[0].content.refresh_token; // TODO refactor those variables this.datadict.instance_url = instanceUrl; this.datadict.refresh_token = refreshToken; @@ -1186,7 +1186,7 @@ class BaseFormView extends PureComponent { this.isResponse = true; return true; } - this.setErrorMsg(response.data.entry[0].content.error); + this.setErrorMsg(responseData.entry[0].content.error); this.isError = true; this.isResponse = true; return false; diff --git a/ui/src/components/BaseFormView/stories/BaseFormView.stories.tsx b/ui/src/components/BaseFormView/stories/BaseFormView.stories.tsx index f1586e3f5..5fdb655cb 100644 --- a/ui/src/components/BaseFormView/stories/BaseFormView.stories.tsx +++ b/ui/src/components/BaseFormView/stories/BaseFormView.stories.tsx @@ -10,8 +10,12 @@ import { import { setUnifiedConfig } from '../../../util/util'; import { GlobalConfig } from '../../../types/globalConfig/globalConfig'; import { Mode } from '../../../constants/modes'; -import { BaseFormProps } from '../BaseFormTypes'; +import { BaseFormProps } from '../../../types/components/BaseFormTypes'; import { Platforms } from '../../../types/globalConfig/pages'; +import { + getGlobalConfigMockGroupsFoInputPage, + getGlobalConfigMockGroupsForConfigPage, +} from '../BaseFormConfigMock'; interface BaseFormStoriesProps extends BaseFormProps { config: GlobalConfig; @@ -99,3 +103,29 @@ export const OuathBasicCloud: Story = { platform: 'cloud', }, }; + +export const ConfigPageGroups: Story = { + args: { + currentServiceState: {}, + serviceName: 'account', + mode: 'create' as Mode, + page: 'configuration', + stanzaName: 'unknownStanza', + handleFormSubmit: fn(), + config: getGlobalConfigMockGroupsForConfigPage(), + platform: 'cloud', + }, +}; + +export const InputPageGroups: Story = { + args: { + currentServiceState: {}, + serviceName: 'demo_input', + mode: 'create' as Mode, + page: 'inputs', + stanzaName: 'unknownStanza', + handleFormSubmit: fn(), + config: getGlobalConfigMockGroupsFoInputPage(), + platform: 'cloud', + }, +}; diff --git a/ui/src/components/BaseFormView/stories/__images__/BaseFormView-config-page-groups-chromium.png b/ui/src/components/BaseFormView/stories/__images__/BaseFormView-config-page-groups-chromium.png new file mode 100644 index 000000000..4d0987c2f --- /dev/null +++ b/ui/src/components/BaseFormView/stories/__images__/BaseFormView-config-page-groups-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:223d96d18f83dd38c7a6d9f294f2ef9e630fa7cd8f9744e0e3f40567aecc2b6c +size 19207 diff --git a/ui/src/components/BaseFormView/stories/__images__/BaseFormView-input-page-groups-chromium.png b/ui/src/components/BaseFormView/stories/__images__/BaseFormView-input-page-groups-chromium.png new file mode 100644 index 000000000..41753b649 --- /dev/null +++ b/ui/src/components/BaseFormView/stories/__images__/BaseFormView-input-page-groups-chromium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f6105f3ba2361c9f48216a2fe30b2c361d1981e13d378f3144934489d4e4d43 +size 19134 diff --git a/ui/src/components/ConfigurationFormView.jsx b/ui/src/components/ConfigurationFormView.jsx index 0d86a8f4d..8bab71111 100644 --- a/ui/src/components/ConfigurationFormView.jsx +++ b/ui/src/components/ConfigurationFormView.jsx @@ -5,10 +5,9 @@ import { _ } from '@splunk/ui-utils/i18n'; import styled from 'styled-components'; import WaitSpinner from '@splunk/react-ui/WaitSpinner'; -import axios from 'axios'; import BaseFormView from './BaseFormView/BaseFormView'; import { StyledButton } from '../pages/EntryPageStyle'; -import { axiosCallWrapper, generateEndPointUrl } from '../util/axiosCallWrapper'; +import { getRequest, generateEndPointUrl } from '../util/api'; import { MODE_CONFIG } from '../constants/modes'; import { WaitSpinnerWrapper } from './table/CustomTableStyle'; import { PAGE_CONF } from '../constants/pages'; @@ -27,7 +26,7 @@ function ConfigurationFormView({ serviceName }) { useEffect(() => { const abortController = new AbortController(); - axiosCallWrapper({ + getRequest({ endpointUrl: generateEndPointUrl(`settings/${encodeURIComponent(serviceName)}`), handleError: true, signal: abortController.signal, @@ -39,16 +38,16 @@ function ConfigurationFormView({ serviceName }) { }, }) .catch((caughtError) => { - if (axios.isCancel(caughtError)) { + if (abortController.signal.aborted) { return null; } throw caughtError; }) - .then((response) => { - if (!response) { + .then((data) => { + if (!data) { return; } - setCurrentServiceState(response.data.entry[0].content); + setCurrentServiceState(data.entry[0].content); }); return () => { diff --git a/ui/src/components/ControlWrapper/ControlWrapper.tsx b/ui/src/components/ControlWrapper/ControlWrapper.tsx index 9bd79f6c9..6719b6625 100644 --- a/ui/src/components/ControlWrapper/ControlWrapper.tsx +++ b/ui/src/components/ControlWrapper/ControlWrapper.tsx @@ -3,7 +3,7 @@ import ControlGroup from '@splunk/react-ui/ControlGroup'; import styled from 'styled-components'; import MarkdownMessage from '../MarkdownMessage/MarkdownMessage'; import CONTROL_TYPE_MAP, { ComponentTypes } from '../../constants/ControlTypeMap'; -import { AnyEntity, UtilControlWrapper } from '../BaseFormView/BaseFormTypes'; +import { AnyEntity, UtilControlWrapper } from '../../types/components/BaseFormTypes'; import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes'; import CustomControl from '../CustomControl/CustomControl'; import { Mode } from '../../constants/modes'; @@ -72,7 +72,6 @@ class ControlWrapper extends React.PureComponent { const { text, link, color, markdownType, token, linkText } = this.props.markdownMessage || {}; let rowView; - // console.log('render custom component', this.props); if (this.props?.entity?.type === 'custom') { const data = { diff --git a/ui/src/components/CustomControl/CustomControl.tsx b/ui/src/components/CustomControl/CustomControl.tsx index f2fab8bb0..810c7cb63 100644 --- a/ui/src/components/CustomControl/CustomControl.tsx +++ b/ui/src/components/CustomControl/CustomControl.tsx @@ -3,7 +3,7 @@ import { _ } from '@splunk/ui-utils/i18n'; import { getUnifiedConfigs } from '../../util/util'; import { getBuildDirPath } from '../../util/script'; import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes'; -import { UtilBaseForm } from '../BaseFormView/BaseFormTypes'; +import { UtilBaseForm } from '../../types/components/BaseFormTypes'; import { GlobalConfig } from '../../types/globalConfig/globalConfig'; import { Mode } from '../../constants/modes'; diff --git a/ui/src/components/CustomControl/CustomControlMockForTest.ts b/ui/src/components/CustomControl/CustomControlMockForTest.ts index 014093379..aea85a060 100644 --- a/ui/src/components/CustomControl/CustomControlMockForTest.ts +++ b/ui/src/components/CustomControl/CustomControlMockForTest.ts @@ -1,7 +1,7 @@ import { Mode } from '../../constants/modes'; import { AcceptableFormValueOrNullish } from '../../types/components/shareableTypes'; import { GlobalConfig } from '../../types/globalConfig/globalConfig'; -import { UtilControlWrapper } from '../BaseFormView/BaseFormTypes'; +import { UtilControlWrapper } from '../../types/components/BaseFormTypes'; export class CustomControlMockForTest { globalConfig: GlobalConfig; diff --git a/ui/src/components/DeleteModal/DeleteModal.test.tsx b/ui/src/components/DeleteModal/DeleteModal.test.tsx index 063e75013..37de666b3 100644 --- a/ui/src/components/DeleteModal/DeleteModal.test.tsx +++ b/ui/src/components/DeleteModal/DeleteModal.test.tsx @@ -51,9 +51,8 @@ it('close model and callback after cancel click', async () => { it('correct delete request', async () => { server.use( - http.delete( - '/servicesNS/nobody/-/restRoot_serviceName/stanzaName', - () => new HttpResponse(undefined, { status: 201 }) + http.delete('/servicesNS/nobody/-/restRoot_serviceName/stanzaName', () => + HttpResponse.json({}, { status: 201 }) ) ); const deleteButton = screen.getByRole('button', { name: /delete/i }); @@ -61,3 +60,26 @@ it('correct delete request', async () => { expect(handleClose).toHaveBeenCalled(); }); + +it('failed delete request', async () => { + const errorMessage = 'Oopsy doopsy'; + server.use( + http.delete('/servicesNS/nobody/-/restRoot_serviceName/stanzaName', () => + HttpResponse.json( + { + messages: [ + { + text: `Unexpected error "" from python handler: "REST Error [400]: Bad Request -- ${errorMessage}". See splunkd.log/python.log for more details.`, + }, + ], + }, + { status: 500 } + ) + ) + ); + const deleteButton = screen.getByRole('button', { name: /delete/i }); + await userEvent.click(deleteButton); + + expect(handleClose).not.toHaveBeenCalled(); + expect(screen.getByText(errorMessage)).toBeInTheDocument(); +}); diff --git a/ui/src/components/DeleteModal/DeleteModal.tsx b/ui/src/components/DeleteModal/DeleteModal.tsx index e7dc8fa42..d989d1ac2 100644 --- a/ui/src/components/DeleteModal/DeleteModal.tsx +++ b/ui/src/components/DeleteModal/DeleteModal.tsx @@ -8,7 +8,7 @@ import { _ } from '@splunk/ui-utils/i18n'; import { generateToast } from '../../util/util'; import { StyledButton } from '../../pages/EntryPageStyle'; -import { axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper'; +import { deleteRequest, generateEndPointUrl } from '../../util/api'; import TableContext from '../../context/TableContext'; import { parseErrorMsg, getFormattedMessage } from '../../util/messageUtil'; import { PAGE_INPUT } from '../../constants/pages'; @@ -51,21 +51,14 @@ class DeleteModal extends Component { this.setState( (prevState) => ({ ...prevState, isDeleting: true, ErrorMsg: '' }), () => { - axiosCallWrapper({ + deleteRequest({ endpointUrl: generateEndPointUrl( `${encodeURIComponent(this.props.serviceName)}/${encodeURIComponent( this.props.stanzaName )}` ), - customHeaders: { 'Content-Type': 'application/x-www-form-urlencoded' }, - method: 'delete', handleError: false, }) - .catch((err) => { - const errorSubmitMsg = parseErrorMsg(err); - this.setState({ ErrorMsg: errorSubmitMsg, isDeleting: false }); - return Promise.reject(err); - }) .then(() => { this.context?.setRowData( update(this.context.rowData, { @@ -75,6 +68,10 @@ class DeleteModal extends Component { this.setState({ isDeleting: false }); this.handleRequestClose(); generateToast(`Deleted "${this.props.stanzaName}"`, 'success'); + }) + .catch((err) => { + const errorSubmitMsg = parseErrorMsg(err); + this.setState({ ErrorMsg: errorSubmitMsg, isDeleting: false }); }); } ); diff --git a/ui/src/components/EntityModal/EntityModal.test.tsx b/ui/src/components/EntityModal/EntityModal.test.tsx index 9b61a8809..c18ddf671 100644 --- a/ui/src/components/EntityModal/EntityModal.test.tsx +++ b/ui/src/components/EntityModal/EntityModal.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { AxiosResponse } from 'axios'; +import { http, HttpResponse } from 'msw'; import EntityModal, { EntityModalProps } from './EntityModal'; import { setUnifiedConfig } from '../../util/util'; import { @@ -18,10 +18,14 @@ import { getConfigWarningMessageAlwaysDisplay, WARNING_MESSAGES_ALWAYS_DISPLAY, } from './TestConfig'; -import { ERROR_AUTH_PROCESS_TERMINATED_TRY_AGAIN } from '../../constants/oAuthErrorMessage'; +import { + ERROR_AUTH_PROCESS_TERMINATED_TRY_AGAIN, + ERROR_STATE_MISSING_TRY_AGAIN, +} from '../../constants/oAuthErrorMessage'; import { Mode } from '../../constants/modes'; -import * as axiosWrapper from '../../util/axiosCallWrapper'; import { StandardPages } from '../../types/components/shareableTypes'; +import { server } from '../../mocks/server'; +import { invariant } from '../../util/invariant'; describe('Oauth field disabled on edit - diableonEdit property', () => { const handleRequestClose = jest.fn(); @@ -242,7 +246,7 @@ describe('EntityModal - auth_endpoint_token_access_type', () => { await userEvent.type(secretField, 'aaa'); } - const addButton = await screen.getByText('Add'); + const addButton = screen.getByText('Add'); expect(addButton).toBeInTheDocument(); const windowOpenSpy = jest.spyOn(window, 'open') as jest.Mock; @@ -384,7 +388,12 @@ describe('Default value', () => { }); describe('Oauth - separated endpoint authorization', () => { - const handleRequestClose = jest.fn(); + let handleRequestClose: jest.Mock<() => void>; + + beforeEach(() => { + handleRequestClose = jest.fn(); + }); + const setUpConfigWithSeparatedEndpoints = () => { const newConfig = getConfigWithSeparatedEndpointsOAuth(); setUnifiedConfig(newConfig); @@ -412,15 +421,16 @@ describe('Oauth - separated endpoint authorization', () => { // mock opening verification window windowOpenSpy.mockImplementation((url) => { - expect(url).toEqual( + expect(url).toContain( 'https://authendpoint/services/oauth2/authorize?response_type=code&client_id=Client%20Id&redirect_uri=http%3A%2F%2Flocalhost%2F' ); - + expect(url).toContain('state='); return { closed: true }; }); await userEvent.click(addButton); - windowOpenSpy.mockRestore(); + + return windowOpenSpy; }; const props = { @@ -459,29 +469,73 @@ describe('Oauth - separated endpoint authorization', () => { }); it('check if correct auth token endpoint created', async () => { + const requestHandler = jest.fn(); + server.use( + http.post('/servicesNS/nobody/-/demo_addon_for_splunk_oauth/oauth', ({ request }) => { + requestHandler(request); + return new HttpResponse(null, { status: 200 }); + }) + ); setUpConfigWithSeparatedEndpoints(); renderModalWithProps(props); - const backendTokenFunction = jest.fn(); await getFilledOauthFields(); const addButton = screen.getByRole('button', { name: /add/i }); - expect(addButton).toBeInTheDocument(); - await spyOnWindowOpen(addButton); + const openFn = await spyOnWindowOpen(addButton); + + expect(openFn).toHaveBeenCalled(); + const url = new URL(openFn.mock.calls[0][0]); + const stateCodeFromUrl = url.searchParams.get('state'); + invariant(stateCodeFromUrl, 'State code is not present in the url'); + + // triggering manually external oauth window behaviour after success authorization + const code = '200'; + window.getMessage({ code, state: stateCodeFromUrl, error: undefined }); + + await waitFor(async () => { + expect(requestHandler).toHaveBeenCalledTimes(1); + }); + + const receivedRequest: Request = requestHandler.mock.calls[0][0]; + const receivedBody = await receivedRequest.text(); + + const params = new URLSearchParams(receivedBody); + const receivedParsedBodyParams: Record = {}; + + params.forEach((value, key) => { + receivedParsedBodyParams[key] = value; + }); - // token is aquired on backend side so only thing we can check is if there is correct url created - jest.spyOn(axiosWrapper, 'axiosCallWrapper').mockImplementation((params) => { - backendTokenFunction((params?.body as unknown as URLSearchParams)?.get('url')); - return new Promise((r) => r({} as unknown as PromiseLike)); + expect(receivedParsedBodyParams).toMatchObject({ + method: 'POST', + url: 'https://tokenendpoint/services/oauth2/token', + grant_type: 'authorization_code', + client_id: 'Client Id', + client_secret: 'Client Secret', + code, + redirect_uri: 'http://localhost/', }); + }); + + it('should throw error if state value mismatch', async () => { + setUpConfigWithSeparatedEndpoints(); + renderModalWithProps(props); + + await getFilledOauthFields(); + const addButton = screen.getByRole('button', { name: /add/i }); + + const openFn = await spyOnWindowOpen(addButton); + + expect(openFn).toHaveBeenCalled(); + const url = new URL(openFn.mock.calls[0][0]); + const stateCodeFromUrl = url.searchParams.get('state'); // triggering manually external oauth window behaviour after success authorization - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (window as any).getMessage({ code: 200, msg: 'testing message for oauth' }); + const code = '200'; + const passedState = `tests${stateCodeFromUrl}`; + window.getMessage({ code, state: passedState, error: undefined }); - // only purpose is to check if backend function receinved correct token url - expect(backendTokenFunction).toHaveBeenCalledWith( - 'https://tokenendpoint/services/oauth2/token' - ); + expect(screen.getByText(ERROR_STATE_MISSING_TRY_AGAIN)).toBeInTheDocument(); }); }); diff --git a/ui/src/components/EntityModal/TestConfig.ts b/ui/src/components/EntityModal/TestConfig.ts index 85884b6e8..6a5a85070 100644 --- a/ui/src/components/EntityModal/TestConfig.ts +++ b/ui/src/components/EntityModal/TestConfig.ts @@ -419,7 +419,7 @@ const entityOauthOauthSeparatedEndpoints = [ auth_code_endpoint: '/services/oauth2/authorize', access_token_endpoint: '/services/oauth2/token', oauth_timeout: 3000, - oauth_state_enabled: false, + oauth_state_enabled: true, display: true, disableonEdit: false, enable: true, diff --git a/ui/src/components/FormModifications/FormModifications.test.tsx b/ui/src/components/FormModifications/FormModifications.test.tsx index de89ebb5d..5a0f21d12 100644 --- a/ui/src/components/FormModifications/FormModifications.test.tsx +++ b/ui/src/components/FormModifications/FormModifications.test.tsx @@ -11,7 +11,7 @@ import { thirdModificationField, } from './TestConfig'; import EntityModal, { EntityModalProps } from '../EntityModal/EntityModal'; -import { EntitiesAllowingModifications } from '../BaseFormView/BaseFormTypes'; +import { EntitiesAllowingModifications } from '../../types/components/BaseFormTypes'; import { invariant } from '../../util/invariant'; const handleRequestClose = jest.fn(); diff --git a/ui/src/components/FormModifications/FormModifications.ts b/ui/src/components/FormModifications/FormModifications.ts index 67117f43d..bace50eaf 100644 --- a/ui/src/components/FormModifications/FormModifications.ts +++ b/ui/src/components/FormModifications/FormModifications.ts @@ -5,7 +5,7 @@ import { BaseFormState, AnyEntity, EntitiesAllowingModifications, -} from '../BaseFormView/BaseFormTypes'; +} from '../../types/components/BaseFormTypes'; import { MarkdownMessageProps } from '../MarkdownMessage/MarkdownMessage'; const VALUE_TO_TRIGGER_UPDATE_FOR_ANY_NOT_LISTED_VALUES = '[[any_other_value]]'; diff --git a/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx b/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx index 52d9ea035..30958e470 100644 --- a/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx +++ b/ui/src/components/MultiInputComponent/MultiInputComponent.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { render, screen } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; import MultiInputComponent, { MultiInputComponentProps } from './MultiInputComponent'; import { server } from '../../mocks/server'; @@ -160,3 +160,80 @@ describe.each(mockedEntries)('handler endpoint data loading', (mockedEntry) => { ); }); }); + +it('should render label (boolean-like)', () => { + renderFeature({ + value: 'true', + controlOptions: { + items: [ + { + label: 'truevalue', + value: true, + }, + { + label: 'falsevalue', + value: false, + }, + { + label: 'optionone', + value: 1, + }, + ], + }, + }); + const inputComponent = screen.getByTestId('multiselect'); + + expect(within(inputComponent).getByText('truevalue')).toBeInTheDocument(); + expect(within(inputComponent).queryByText('falsevalue')).not.toBeInTheDocument(); + expect(within(inputComponent).queryByText('optionone')).not.toBeInTheDocument(); +}); + +it('should render singe value (numeric)', () => { + renderFeature({ + value: 1, + controlOptions: { + items: [ + { + label: 'label1', + value: 1, + }, + { + label: 'label2', + value: 2, + }, + ], + }, + }); + const inputComponent = screen.getByTestId('multiselect'); + + expect(within(inputComponent).getByText('label1')).toBeInTheDocument(); + expect(within(inputComponent).queryByText('label2')).not.toBeInTheDocument(); +}); + +it('should render two values (number + boolean)', () => { + renderFeature({ + value: '1;false', + controlOptions: { + delimiter: ';', + items: [ + { + label: 'label1', + value: 1, + }, + { + label: 'label2', + value: false, + }, + { + label: 'label3', + value: 3, + }, + ], + }, + }); + const inputComponent = screen.getByTestId('multiselect'); + + expect(within(inputComponent).getByText('label1')).toBeInTheDocument(); + expect(within(inputComponent).getByText('label2')).toBeInTheDocument(); + expect(within(inputComponent).queryByText('label3')).not.toBeInTheDocument(); +}); diff --git a/ui/src/components/MultiInputComponent/MultiInputComponent.tsx b/ui/src/components/MultiInputComponent/MultiInputComponent.tsx index 28e2eb446..317ebe89a 100644 --- a/ui/src/components/MultiInputComponent/MultiInputComponent.tsx +++ b/ui/src/components/MultiInputComponent/MultiInputComponent.tsx @@ -4,10 +4,11 @@ import styled from 'styled-components'; import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import { z } from 'zod'; -import { AxiosCallType, axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper'; -import { filterResponse } from '../../util/util'; +import { RequestParams, generateEndPointUrl, getRequest } from '../../util/api'; +import { filterResponse, FilterResponseParams } from '../../util/util'; import { MultipleSelectCommonOptions } from '../../types/globalConfig/entities'; import { invariant } from '../../util/invariant'; +import { AcceptableFormValue } from '../../types/components/shareableTypes'; const MultiSelectWrapper = styled(Multiselect)` width: 320px !important; @@ -23,7 +24,7 @@ export interface MultiInputComponentProps { field: string; controlOptions: z.TypeOf; disabled?: boolean; - value?: string; + value?: AcceptableFormValue; error?: boolean; dependencyValues?: Record; } @@ -62,7 +63,7 @@ function MultiInputComponent(props: MultiInputComponentProps) { return itemList.map((item) => ( )); @@ -93,19 +94,19 @@ function MultiInputComponent(props: MultiInputComponentProps) { handleError: true, params: { count: -1 }, endpointUrl: url, - } satisfies AxiosCallType; + } satisfies RequestParams; if (dependencyValues) { apiCallOptions.params = { ...apiCallOptions.params, ...dependencyValues }; } if (!dependencies || dependencyValues) { setLoading(true); - axiosCallWrapper(apiCallOptions) - .then((response) => { + getRequest<{ entry: FilterResponseParams }>(apiCallOptions) + .then((data) => { if (current) { setOptions( generateOptions( filterResponse( - response.data.entry, + data.entry, labelField, valueField, allowList, @@ -113,10 +114,9 @@ function MultiInputComponent(props: MultiInputComponentProps) { ) ) ); - setLoading(false); } }) - .catch(() => { + .finally(() => { if (current) { setLoading(false); } @@ -133,7 +133,7 @@ function MultiInputComponent(props: MultiInputComponentProps) { const effectiveDisabled = loading ? true : disabled; const loadingIndicator = loading ? : null; - const valueList = value ? value.split(delimiter) : []; + const valueList = value ? String(value).split(delimiter) : []; return ( <> diff --git a/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx b/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx index 032cc1b49..eec9726e6 100644 --- a/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx +++ b/ui/src/components/SingleInputComponent/SingleInputComponent.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { render, screen } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; import SingleInputComponent, { SingleInputComponentProps } from './SingleInputComponent'; import { setUnifiedConfig } from '../../util/util'; @@ -132,3 +132,38 @@ describe.each(mockedEntries)('handler endpoint data loading', (entry) => { ); }); }); + +it('should render Select... when value does not exist in autoCompleteFields', () => { + renderFeature({ + value: 'notExistingValue', + controlOptions: { + autoCompleteFields: [ + { + label: 'label1', + value: 'value1', + }, + ], + }, + }); + const inputComponent = screen.getByRole('combobox'); + expect(inputComponent).toBeInTheDocument(); + expect(within(inputComponent).getByText('Select...')).toBeInTheDocument(); +}); + +it.each([ + { value: true, autoCompleteFields: [{ label: 'trueLabel', value: true }] }, + { + value: false, + autoCompleteFields: [{ label: 'falseLabel', value: false }], + }, + { value: 0, autoCompleteFields: [{ label: 'falseLabel', value: '0' }] }, + { value: 0, autoCompleteFields: [{ label: 'falseLabel', value: 0 }] }, +])('should render label with value $value', ({ value, autoCompleteFields }) => { + renderFeature({ + value, + controlOptions: { autoCompleteFields }, + }); + const inputComponent = screen.getByRole('combobox'); + const { label } = autoCompleteFields[0]; + expect(within(inputComponent).getByText(label)).toBeInTheDocument(); +}); diff --git a/ui/src/components/SingleInputComponent/SingleInputComponent.tsx b/ui/src/components/SingleInputComponent/SingleInputComponent.tsx index c4b8b92fc..126a71892 100755 --- a/ui/src/components/SingleInputComponent/SingleInputComponent.tsx +++ b/ui/src/components/SingleInputComponent/SingleInputComponent.tsx @@ -7,11 +7,11 @@ import styled from 'styled-components'; import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import { z } from 'zod'; -import { AxiosCallType, axiosCallWrapper, generateEndPointUrl } from '../../util/axiosCallWrapper'; +import { RequestParams, generateEndPointUrl, getRequest } from '../../util/api'; import { SelectCommonOptions } from '../../types/globalConfig/entities'; -import { filterResponse } from '../../util/util'; +import { filterResponse, FilterResponseParams } from '../../util/util'; import { getValueMapTruthyFalse } from '../../util/considerFalseAndTruthy'; -import { StandardPages } from '../../types/components/shareableTypes'; +import { AcceptableFormValue, StandardPages } from '../../types/components/shareableTypes'; const SelectWrapper = styled(Select)` width: 320px !important; @@ -27,7 +27,7 @@ const StyledDiv = styled.div` } `; -type BasicFormItem = { value: string | number | boolean; label: string }; +type BasicFormItem = { value: AcceptableFormValue; label: string }; type FormItem = | BasicFormItem @@ -39,9 +39,9 @@ type FormItem = export interface SingleInputComponentProps { id?: string; disabled?: boolean; - value: string; + value: AcceptableFormValue; error?: boolean; - handleChange: (field: string, value: string | number | boolean) => void; + handleChange: (field: string, value: string) => void; field: string; dependencyValues?: Record; controlOptions: z.TypeOf & { @@ -74,8 +74,8 @@ function SingleInputComponent(props: SingleInputComponentProps) { hideClearBtn, } = controlOptions; - const handleChange = (e: unknown, obj: { value: string | number | boolean }) => { - restProps.handleChange(field, obj.value); + const handleChange = (e: unknown, obj: { value: AcceptableFormValue }) => { + restProps.handleChange(field, String(obj.value)); }; const Option = createSearchChoice ? ComboBox.Option : Select.Option; const Heading = createSearchChoice ? ComboBox.Heading : Select.Heading; @@ -83,10 +83,16 @@ function SingleInputComponent(props: SingleInputComponentProps) { function generateOptions(items: FormItem[]) { const data: ReactElement[] = []; items.forEach((item) => { - if ('value' in item && item.value && item.label) { - // splunk will mape those when sending post form + if ( + 'value' in item && + item.value !== null && + item.value !== undefined && + item.value !== '' && + item.label + ) { + // splunk will maps those when sending post form // so worth doing it earlier to keep same state before and after post - const itemValue = getValueMapTruthyFalse(item.value, props.page); + const itemValue = String(getValueMapTruthyFalse(item.value, props.page)); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore JSX element type 'Option' does not have any construct or call signatures. data.push(