Skip to content

Commit

Permalink
feat: support CRON as interval (#1346)
Browse files Browse the repository at this point in the history
**Issue number:** ADDON-73348

## Summary

Add option to use cron in an interval type field

### Changes

* Using standard CRON syntax is now allowed in input interval
* Long form Interval entity was updated with new regex
* Schema.json was modified 
* New UTs and UI tests were created

### User experience

* User is able to use CRON as input interval

## Checklist

If your change doesn't seem to apply, please leave them unchecked.

* [x] I have performed a self-review of this change
* [x] Changes have been tested
* [x] Changes are documented
* [x] PR title follows [conventional commit
semantics](https://www.conventionalcommits.org/en/v1.0.0/)

---------

Co-authored-by: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com>
  • Loading branch information
lplonka-splunk and srv-rr-github-token authored Oct 15, 2024
1 parent d9c396b commit 8c5a981
Show file tree
Hide file tree
Showing 19 changed files with 187 additions and 41 deletions.
14 changes: 13 additions & 1 deletion docs/entity/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,14 +505,25 @@ The Oauth type entity enables us to use Oauth2.0 for user authentication. Visit
## `Interval`

A [Text](#text) field used to specify [interval](https://docs.splunk.com/Documentation/Splunk/latest/Admin/Inputsconf#Scripted_Input:)
value, i.e. a number greater than or equal to 0, or -1.
value, i.e. a number greater than or equal to 0, CRON interval or -1.

<h3> Options </h3>

| Property | Type | Description |
| -------- | ---------------------------- | ----------------------- |
| range | list of numbers (2 elements) | Range of allowed values |


Supported CRON schedule:

* "<minute> <hour> <day of month> <month> <day of week>"
* Cron special characters are acceptable. ("*", ",", "/", "-" )

Names of months or days are not supported.

Note: Range option is not supposed to be used with CRON interval.


See the following example:

```json
Expand All @@ -526,6 +537,7 @@ See the following example:
}
```


## `Index`

A field used to specify [index](https://docs.splunk.com/Splexicon:Index).
Expand Down
12 changes: 10 additions & 2 deletions splunk_add_on_ucc_framework/entity/interval_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@

from splunk_add_on_ucc_framework.entity.entity import Entity

CRON_REGEX = (
r"^("
r"(?:-1|\d+(?:\.\d+)?)" # Non-negative number or -1
r"|"
r"(([\*\d{1,2}\,\-\/]+\s){4}[\*\d{1,2}\,\-\/]+)" # CRON interval
r")$"
)


class IntervalEntity(Entity):
def short_form(self) -> Dict[str, Any]:
Expand All @@ -30,8 +38,8 @@ def long_form(self) -> Dict[str, Any]:
"validators": [
{
"type": "regex",
"errorMsg": f"{self['label']} must be either a non-negative number or -1.",
"pattern": r"^(?:-1|\d+(?:\.\d+)?)$",
"errorMsg": f"{self['label']} must be either a non-negative number, CRON interval or -1.",
"pattern": CRON_REGEX,
}
],
}
Expand Down
5 changes: 3 additions & 2 deletions splunk_add_on_ucc_framework/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,8 @@
{
"type": "string",
"enum": [
"ERROR", "CRITICAL"
"ERROR",
"CRITICAL"
]
}
]
Expand Down Expand Up @@ -3127,7 +3128,7 @@
},
{
"type": "string",
"pattern": "^(?:-1|\\d+(?:\\.\\d+)?)$"
"pattern": "^((?:-1|\\d+(?:\\.\\d+)?)|(([\\*\\d{1,2}\\,\\-\\/]+\\s){4}[\\*\\d{1,2}\\,\\-\\/]+))$"
}
]
},
Expand Down
5 changes: 3 additions & 2 deletions tests/smoke/test_ucc_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path
from typing import Dict, Any

from splunk_add_on_ucc_framework.entity.interval_entity import CRON_REGEX
from tests.smoke import helpers
from tests.unit import helpers as unit_helpers
import addonfactory_splunk_conf_parser_lib as conf_parser
Expand Down Expand Up @@ -631,8 +632,8 @@ def _compare_interval_entities(
"type": "text",
"validators": [
{
"errorMsg": "Interval must be either a non-negative number or -1.",
"pattern": "^(?:-1|\\d+(?:\\.\\d+)?)$",
"errorMsg": "Interval must be either a non-negative number, CRON interval or -1.",
"pattern": CRON_REGEX,
"type": "regex",
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
encrypted=False,
default=None,
validator=validator.Pattern(
regex=r"""^(?:-1|\d+(?:\.\d+)?)$""",
regex=r"""^((?:-1|\d+(?:\.\d+)?)|(([\*\d{1,2}\,\-\/]+\s){4}[\*\d{1,2}\,\-\/]+))$""",
)
),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
encrypted=False,
default=None,
validator=validator.Pattern(
regex=r"""^(?:-1|\d+(?:\.\d+)?)$""",
regex=r"""^((?:-1|\d+(?:\.\d+)?)|(([\*\d{1,2}\,\-\/]+\s){4}[\*\d{1,2}\,\-\/]+))$""",
)
),
field.RestField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
encrypted=False,
default=None,
validator=validator.Pattern(
regex=r"""^(?:-1|\d+(?:\.\d+)?)$""",
regex=r"""^((?:-1|\d+(?:\.\d+)?)|(([\*\d{1,2}\,\-\/]+\s){4}[\*\d{1,2}\,\-\/]+))$""",
)
),
field.RestField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
encrypted=False,
default=None,
validator=validator.Pattern(
regex=r"""^(?:-1|\d+(?:\.\d+)?)$""",
regex=r"""^((?:-1|\d+(?:\.\d+)?)|(([\*\d{1,2}\,\-\/]+\s){4}[\*\d{1,2}\,\-\/]+))$""",
)
),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@
"meta": {
"name": "test_addon",
"restRoot": "test_addon",
"version": "5.47.0+a2b585f01",
"version": "5.50.1+8cbb9b3c",
"displayName": "This is my add-on",
"schemaVersion": "0.0.7",
"_uccVersion": "5.47.0"
"schemaVersion": "0.0.8",
"_uccVersion": "5.50.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,6 @@
"version": "1.1.1",
"displayName": "Splunk UCC test Add-on",
"schemaVersion": "0.0.7",
"_uccVersion": "5.48.1"
"_uccVersion": "5.50.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1864,10 +1864,10 @@
"meta": {
"name": "Splunk_TA_UCCExample",
"restRoot": "splunk_ta_uccexample",
"version": "5.49.0+cc63ee532",
"version": "5.50.1+8cbb9b3c",
"displayName": "Splunk UCC test Add-on",
"schemaVersion": "0.0.8",
"_uccVersion": "5.50.0",
"_uccVersion": "5.50.1",
"supportedThemes": [
"light",
"dark"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
encrypted=False,
default=None,
validator=validator.Pattern(
regex=r"""^(?:-1|\d+(?:\.\d+)?)$""",
regex=r"""^((?:-1|\d+(?:\.\d+)?)|(([\*\d{1,2}\,\-\/]+\s){4}[\*\d{1,2}\,\-\/]+))$""",
)
),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1172,9 +1172,9 @@
"meta": {
"name": "Splunk_TA_UCCExample",
"restRoot": "splunk_ta_uccexample",
"version": "5.47.0+a2b585f01",
"version": "5.50.1+8cbb9b3c",
"displayName": "Splunk UCC test Add-on",
"schemaVersion": "0.0.7",
"_uccVersion": "5.47.0"
"schemaVersion": "0.0.8",
"_uccVersion": "5.50.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -481,9 +481,9 @@
"meta": {
"name": "Splunk_TA_UCCExample",
"restRoot": "splunk_ta_uccexample",
"version": "5.47.0+a2b585f01",
"version": "5.50.1+8cbb9b3c",
"displayName": "Splunk UCC test Add-on",
"schemaVersion": "0.0.7",
"_uccVersion": "5.47.0"
"schemaVersion": "0.0.8",
"_uccVersion": "5.50.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
"meta": {
"name": "Splunk_TA_UCCExample",
"restRoot": "splunk_ta_uccexample",
"version": "5.47.0+a2b585f01",
"version": "5.50.1+8cbb9b3c",
"displayName": "Splunk UCC test Add-on",
"schemaVersion": "0.0.7",
"_uccVersion": "5.47.0"
"_uccVersion": "5.50.1"
}
}
84 changes: 82 additions & 2 deletions tests/ui/test_input_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ def test_example_input_one_valid_input_interval(
input_page.entity1.interval.set_value("abc")
self.assert_util(
input_page.entity1.save,
r"Interval must be either a non-negative number or -1.",
r"Interval must be either a non-negative number, CRON interval or -1.",
left_args={"expect_error": True},
)

Expand Down Expand Up @@ -1599,7 +1599,7 @@ def test_example_input_two_valid_input_interval(
input_page.entity2.interval.set_value("abc")
self.assert_util(
input_page.entity2.save,
r"Interval must be either a non-negative number or -1.",
r"Interval must be either a non-negative number, CRON interval or -1.",
left_args={"expect_error": True},
)

Expand Down Expand Up @@ -2533,3 +2533,83 @@ def test_inputs_textarea_scroll(
input_page.entity1.text_area.scroll("UP", 40)
screenshot_after = input_page.entity1.text_area.screenshot()
self.assert_util(screnshot_before, screenshot_after, operator="!=")

@pytest.mark.execute_enterprise_cloud_true
@pytest.mark.forwarder
@pytest.mark.input
@pytest.mark.parametrize(
"interval",
[
"-1",
"1",
"0 0,11 2 */2 *",
"* * * * *",
],
)
def test_example_inputs_with_valid_interval(
self, ucc_smartx_selenium_helper, ucc_smartx_rest_helper, interval
):
input_page = InputPage(ucc_smartx_selenium_helper, ucc_smartx_rest_helper)
# for input_pos, interval in enumerate(intervals, 1):
name = "dummy_input"
input_page.create_new_input.select("Example Input One")
input_page.entity1.example_account.wait_for_values()
input_page.entity1.example_account.select("test_input")
input_page.entity1.object.set_value("test_object")
input_page.entity1.name.set_value(name)
input_page.entity1.object_fields.set_value("test_field")
input_page.entity1.text_area.set_value("line1\nline2\nline3\nline4\nline5")

input_page.entity1.interval.set_value(interval)

input_page.entity1.save_btn.click()
input_page.table.wait_for_rows_to_appear(1)

self.assert_util(
input_page.table.get_table()[name],
{
"name": name,
"account": "test_input",
"interval": interval,
"index": "default",
"status": "Active",
"actions": "Edit | Clone | Search | Delete",
},
)

backend_stanza = input_page.backend_conf.get_stanza(
f"example_input_one://{name}"
)
assert backend_stanza.get("interval") == interval

@pytest.mark.execute_enterprise_cloud_true
@pytest.mark.forwarder
@pytest.mark.input
@pytest.mark.parametrize(
"interval",
[
"-2",
"0a 0,11 2 */2 *",
"a b * * *",
],
)
def test_example_inputs_with_not_valid_interval(
self, ucc_smartx_selenium_helper, ucc_smartx_rest_helper, interval
):
input_page = InputPage(ucc_smartx_selenium_helper, ucc_smartx_rest_helper)
name = "dummy_input"
input_page.create_new_input.select("Example Input One")
input_page.entity1.example_account.wait_for_values()
input_page.entity1.example_account.select("test_input")
input_page.entity1.object.set_value("test_object")
input_page.entity1.name.set_value(name)
input_page.entity1.object_fields.set_value("test_field")
input_page.entity1.text_area.set_value("line1\nline2\nline3\nline4\nline5")

input_page.entity1.interval.set_value(interval)

self.assert_util(
input_page.entity2.save,
"Interval must be either a non-negative number, CRON interval or -1.",
left_args={"expect_error": True},
)
22 changes: 12 additions & 10 deletions tests/unit/entity/test_interval_entity.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from splunk_add_on_ucc_framework.entity import IntervalEntity

from splunk_add_on_ucc_framework.entity.interval_entity import CRON_REGEX


def test_interval_minimal_definition():
definition = {"type": "interval", "field": "interval", "label": "Interval"}
Expand All @@ -13,8 +15,8 @@ def test_interval_minimal_definition():
"type": "text",
"validators": [
{
"errorMsg": "Interval must be either a non-negative number or -1.",
"pattern": "^(?:-1|\\d+(?:\\.\\d+)?)$",
"errorMsg": "Interval must be either a non-negative number, CRON interval or -1.",
"pattern": CRON_REGEX,
"type": "regex",
}
],
Expand Down Expand Up @@ -46,8 +48,8 @@ def test_interval_full_definition():
"required": True,
"validators": [
{
"errorMsg": "Interval input must be either a non-negative number or -1.",
"pattern": "^(?:-1|\\d+(?:\\.\\d+)?)$",
"errorMsg": "Interval input must be either a non-negative number, CRON interval or -1.",
"pattern": CRON_REGEX,
"type": "regex",
},
{
Expand All @@ -70,8 +72,8 @@ def test_interval_migration():
"required": True,
"validators": [
{
"errorMsg": "Interval must be either a non-negative number or -1.",
"pattern": "^(?:-1|\\d+(?:\\.\\d+)?)$",
"errorMsg": "Interval must be either a non-negative number, CRON interval or -1.",
"pattern": CRON_REGEX,
"type": "regex",
},
],
Expand Down Expand Up @@ -104,8 +106,8 @@ def test_interval_migration_with_range():
"required": True,
"validators": [
{
"errorMsg": "Interval must be either a non-negative number or -1.",
"pattern": "^(?:-1|\\d+(?:\\.\\d+)?)$",
"errorMsg": "Interval must be either a non-negative number, CRON interval or -1.",
"pattern": CRON_REGEX,
"type": "regex",
},
{
Expand Down Expand Up @@ -152,8 +154,8 @@ def test_interval_migration_wrong_field():
"label": "Other field",
"validators": [
{
"errorMsg": "Other field must be either a non-negative number or -1.",
"pattern": "^(?:-1|\\d+(?:\\.\\d+)?)$",
"errorMsg": "Other field must be either a non-negative number, CRON interval or -1.",
"pattern": CRON_REGEX,
"type": "regex",
},
],
Expand Down
Loading

0 comments on commit 8c5a981

Please sign in to comment.