Skip to content

Commit 94037ce

Browse files
committed
Created base aql platform and fixes
1 parent d3dba4e commit 94037ce

File tree

48 files changed

+265
-370
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+265
-370
lines changed

uncoder-core/app/translator/core/exceptions/core.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,7 @@ class InvalidYamlStructure(InvalidRuleStructure):
7777

7878
class InvalidJSONStructure(InvalidRuleStructure):
7979
rule_type: str = "JSON"
80+
81+
82+
class InvalidXMLStructure(InvalidRuleStructure):
83+
rule_type: str = "XML"

uncoder-core/app/translator/core/mixins/rule.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import json
2+
from typing import Union
23

4+
import xmltodict
35
import yaml
46

5-
from app.translator.core.exceptions.core import InvalidJSONStructure, InvalidYamlStructure
7+
from app.translator.core.exceptions.core import InvalidJSONStructure, InvalidXMLStructure, InvalidYamlStructure
68
from app.translator.core.mitre import MitreConfig
79

810

@@ -36,5 +38,13 @@ def parse_mitre_attack(self, tags: list[str]) -> dict[str, list]:
3638
result["techniques"].append(technique)
3739
elif tactic := self.mitre_config.get_tactic(tag):
3840
result["tactics"].append(tactic)
39-
4041
return result
42+
43+
44+
class XMLRuleMixin:
45+
@staticmethod
46+
def load_rule(text: Union[str, bytes]) -> dict:
47+
try:
48+
return xmltodict.parse(text)
49+
except Exception as err:
50+
raise InvalidXMLStructure(error=str(err)) from err

uncoder-core/app/translator/core/models/query_container.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ class RawQueryContainer:
5656
meta_info: MetaInfoContainer = field(default_factory=MetaInfoContainer)
5757

5858

59+
@dataclass
60+
class RawQueryDictContainer:
61+
query: dict
62+
language: str
63+
meta_info: dict
64+
65+
5966
@dataclass
6067
class TokenizedQueryContainer:
6168
tokens: list[TOKEN_TYPE]

uncoder-core/app/translator/core/parser.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@
3232

3333
class QueryParser(ABC):
3434
wrapped_with_comment_pattern: str = None
35+
details: PlatformDetails = None
3536

3637
def remove_comments(self, text: str) -> str:
37-
return re.sub(self.wrapped_with_comment_pattern, "\n", text, flags=re.MULTILINE).strip()
38+
if self.wrapped_with_comment_pattern:
39+
return re.sub(self.wrapped_with_comment_pattern, "\n", text, flags=re.MULTILINE).strip()
40+
41+
return text
3842

3943
def parse_raw_query(self, text: str, language: str) -> RawQueryContainer:
4044
return RawQueryContainer(query=text, language=language)
@@ -47,7 +51,6 @@ def parse(self, raw_query_container: RawQueryContainer) -> TokenizedQueryContain
4751
class PlatformQueryParser(QueryParser, ABC):
4852
mappings: BasePlatformMappings = None
4953
tokenizer: QueryTokenizer = None
50-
details: PlatformDetails = None
5154
platform_functions: PlatformFunctions = None
5255

5356
def get_fields_tokens(self, tokens: list[Union[FieldValue, Keyword, Identifier]]) -> list[Field]:

uncoder-core/app/translator/core/render_cti.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020

2121
from app.translator.core.models.iocs import IocsChunkValue
22+
from app.translator.core.models.platform_details import PlatformDetails
2223

2324

2425
class RenderCTI:
@@ -31,6 +32,7 @@ class RenderCTI:
3132
final_result_for_many: str = "union * | where ({result})\n"
3233
final_result_for_one: str = "union * | where {result}\n"
3334
default_mapping = None
35+
details: PlatformDetails = None
3436

3537
def create_field_value(self, field: str, value: str, generic_field: str) -> str: # noqa: ARG002
3638
return self.field_value_template.format(key=field, value=value)

uncoder-core/app/translator/core/tokenizer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class QueryTokenizer(BaseTokenizer):
5252
single_value_operators_map: ClassVar[dict[str, str]] = {}
5353
# used to generate re pattern. so the keys order is important
5454
multi_value_operators_map: ClassVar[dict[str, str]] = {}
55+
# used to generate re pattern. so the keys order is important
56+
fields_operator_map: ClassVar[dict[str, str]] = {}
5557
operators_map: ClassVar[dict[str, str]] = {} # used to generate re pattern. so the keys order is important
5658

5759
logical_operator_pattern = r"^(?P<logical_operator>and|or|not|AND|OR|NOT)\s+"
@@ -73,7 +75,11 @@ class QueryTokenizer(BaseTokenizer):
7375
def __init_subclass__(cls, **kwargs):
7476
cls._validate_re_patterns()
7577
cls.value_pattern = cls.base_value_pattern.replace("___value_pattern___", cls._value_pattern)
76-
cls.operators_map = {**cls.single_value_operators_map, **cls.multi_value_operators_map}
78+
cls.operators_map = {
79+
**cls.single_value_operators_map,
80+
**cls.multi_value_operators_map,
81+
**cls.fields_operator_map,
82+
}
7783
cls.operator_pattern = rf"""(?:___field___\s*(?P<operator>(?:{'|'.join(cls.operators_map)})))\s*"""
7884

7985
@classmethod
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from app.translator.platforms.arcsight.renders.arcsight_cti import ArcsightKeyword
1+
from app.translator.platforms.arcsight.renders.arcsight_cti import ArcsightKeyword # noqa: F401
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ARCSIGHT_QUERY_DETAILS = {
2+
"platform_id": "arcsight",
3+
"name": "ArcSight Query",
4+
"group_name": "ArcSight",
5+
"group_id": "arcsight",
6+
"platform_name": "Query",
7+
"alt_platform_name": "CEF",
8+
}

uncoder-core/app/translator/platforms/arcsight/mappings/__init__.py

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
DEFAULT_ARCSIGHT_MAPPING = {
2+
"SourceIP": "sourceAddress",
3+
"DestinationIP": "destinationAddress",
4+
"Domain": "destinationDnsDomain",
5+
"URL": "requestUrl",
6+
"HashMd5": "fileHash",
7+
"HashSha1": "fileHash",
8+
"HashSha256": "fileHash",
9+
"HashSha512": "fileHash",
10+
"Emails": "sender-address",
11+
"Files": "winlog.event_data.TargetFilename",
12+
}

uncoder-core/app/translator/platforms/arcsight/renders/__init__.py

Whitespace-only changes.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from app.translator.platforms.athena.parsers.athena import AthenaQueryParser
2-
from app.translator.platforms.athena.renders.athena import AthenaQueryRender
3-
from app.translator.platforms.athena.renders.athena_cti import AthenaCTI
1+
from app.translator.platforms.athena.parsers.athena import AthenaQueryParser # noqa: F401
2+
from app.translator.platforms.athena.renders.athena import AthenaQueryRender # noqa: F401
3+
from app.translator.platforms.athena.renders.athena_cti import AthenaCTI # noqa: F401

uncoder-core/app/translator/platforms/base/aql/__init__.py

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
UTF8_PAYLOAD_PATTERN = r"UTF8\(payload\)"
2+
NUM_VALUE_PATTERN = r"(?P<num_value>\d+(?:\.\d+)*)"
3+
SINGLE_QUOTES_VALUE_PATTERN = r"""'(?P<s_q_value>(?:[:a-zA-Z\*0-9=+%#\-\/\\,_".$&^@!\(\)\{\}\s]|'')*)'"""
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from app.translator.core.escape_manager import EscapeManager
2+
3+
4+
class AQLEscapeManager(EscapeManager):
5+
...
6+
7+
8+
aql_escape_manager = AQLEscapeManager()

uncoder-core/app/translator/platforms/base/aql/parsers/__init__.py

Whitespace-only changes.

uncoder-core/app/translator/platforms/base/aql/renders/__init__.py

Whitespace-only changes.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
Uncoder IO Community Edition License
3+
-----------------------------------------------------------------
4+
Copyright (c) 2024 SOC Prime, Inc.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
-----------------------------------------------------------------
18+
"""
19+
from typing import Union
20+
21+
from app.translator.const import DEFAULT_VALUE_TYPE
22+
from app.translator.core.custom_types.values import ValueType
23+
from app.translator.core.render import BaseQueryFieldValue, PlatformQueryRender
24+
from app.translator.platforms.base.aql.escape_manager import aql_escape_manager
25+
from app.translator.platforms.base.aql.mapping import AQLLogSourceSignature, AQLMappings, aql_mappings
26+
27+
28+
class AQLFieldValue(BaseQueryFieldValue):
29+
escape_manager = aql_escape_manager
30+
31+
def apply_value(self, value: Union[str, int], value_type: str = ValueType.value) -> Union[str, int]: # noqa: ARG002
32+
if isinstance(value, str):
33+
value = value.replace("_", "__").replace("%", "%%").replace("\\'", "%").replace("'", '"')
34+
if value.endswith("\\\\%"):
35+
value = value.replace("\\\\%", "\\%")
36+
return value
37+
38+
def _apply_value(self, value: Union[str, int]) -> Union[str, int]:
39+
if isinstance(value, str) and "\\" in value:
40+
return value
41+
return self.apply_value(value)
42+
43+
def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str:
44+
if isinstance(value, list):
45+
return f"({self.or_token.join([self.equal_modifier(field=field, value=v) for v in value])})"
46+
if field == "UTF8(payload)":
47+
return f"UTF8(payload) ILIKE '{self.apply_value(value)}'"
48+
if isinstance(value, int):
49+
return f'"{field}"={value}'
50+
51+
return f"\"{field}\"='{self._apply_value(value)}'"
52+
53+
def less_modifier(self, field: str, value: Union[int, str]) -> str:
54+
if isinstance(value, int):
55+
return f'"{field}"<{value}'
56+
return f"\"{field}\"<'{self._apply_value(value)}'"
57+
58+
def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str:
59+
if isinstance(value, int):
60+
return f'"{field}"<={value}'
61+
return f"\"{field}\"<='{self._apply_value(value)}'"
62+
63+
def greater_modifier(self, field: str, value: Union[int, str]) -> str:
64+
if isinstance(value, int):
65+
return f'"{field}">{value}'
66+
return f"\"{field}\">'{self._apply_value(value)}'"
67+
68+
def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str:
69+
if isinstance(value, int):
70+
return f'"{field}">={value}'
71+
return f"\"{field}\">='{self._apply_value(value)}'"
72+
73+
def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str:
74+
if isinstance(value, list):
75+
return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})"
76+
if isinstance(value, int):
77+
return f'"{field}"!={value}'
78+
return f"\"{field}\"!='{self._apply_value(value)}'"
79+
80+
def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str:
81+
if isinstance(value, list):
82+
return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})"
83+
return f"\"{field}\" ILIKE '%{self._apply_value(value)}%'"
84+
85+
def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str:
86+
if isinstance(value, list):
87+
return f"({self.or_token.join(self.endswith_modifier(field=field, value=v) for v in value)})"
88+
return f"\"{field}\" ILIKE '%{self._apply_value(value)}'"
89+
90+
def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str:
91+
if isinstance(value, list):
92+
return f"({self.or_token.join(self.startswith_modifier(field=field, value=v) for v in value)})"
93+
return f"\"{field}\" ILIKE '{self._apply_value(value)}%'"
94+
95+
def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str:
96+
if isinstance(value, list):
97+
return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})"
98+
return f"\"{field}\" IMATCHES '{value}'"
99+
100+
def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str:
101+
if isinstance(value, list):
102+
return f"({self.or_token.join(self.keywords(field=field, value=v) for v in value)})"
103+
return f"UTF8(payload) ILIKE '%{self.apply_value(value)}%'"
104+
105+
106+
class AQLQueryRender(PlatformQueryRender):
107+
mappings: AQLMappings = aql_mappings
108+
109+
or_token = "OR"
110+
and_token = "AND"
111+
not_token = "NOT"
112+
113+
field_value_map = AQLFieldValue(or_token=or_token)
114+
query_pattern = "{prefix} AND {query} {functions}"
115+
116+
def generate_prefix(self, log_source_signature: AQLLogSourceSignature) -> str:
117+
table = str(log_source_signature)
118+
extra_condition = log_source_signature.extra_condition
119+
return f"SELECT UTF8(payload) FROM {table} WHERE {extra_condition}"
120+
121+
def wrap_with_comment(self, value: str) -> str:
122+
return f"/* {value} */"

uncoder-core/app/translator/platforms/qradar/tokenizer.py renamed to uncoder-core/app/translator/platforms/base/aql/tokenizer.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1616
-----------------------------------------------------------------
1717
"""
18-
1918
import re
2019
from typing import Any, ClassVar, Union
2120

@@ -24,12 +23,12 @@
2423
from app.translator.core.models.field import FieldValue, Keyword
2524
from app.translator.core.models.identifier import Identifier
2625
from app.translator.core.tokenizer import QueryTokenizer
27-
from app.translator.platforms.qradar.const import NUM_VALUE_PATTERN, SINGLE_QUOTES_VALUE_PATTERN, UTF8_PAYLOAD_PATTERN
28-
from app.translator.platforms.qradar.escape_manager import qradar_escape_manager
26+
from app.translator.platforms.base.aql.const import NUM_VALUE_PATTERN, SINGLE_QUOTES_VALUE_PATTERN, UTF8_PAYLOAD_PATTERN
27+
from app.translator.platforms.base.aql.escape_manager import aql_escape_manager
2928
from app.translator.tools.utils import get_match_group
3029

3130

32-
class QradarTokenizer(QueryTokenizer):
31+
class AQLTokenizer(QueryTokenizer):
3332
single_value_operators_map: ClassVar[dict[str, str]] = {
3433
"=": OperatorType.EQ,
3534
"<=": OperatorType.LTE,
@@ -49,7 +48,7 @@ class QradarTokenizer(QueryTokenizer):
4948
_value_pattern = rf"{NUM_VALUE_PATTERN}|{bool_value_pattern}|{SINGLE_QUOTES_VALUE_PATTERN}"
5049
multi_value_pattern = rf"""\((?P<{ValueType.multi_value}>[:a-zA-Z\"\*0-9=+%#\-_\/\\'\,.&^@!\(\s]*)\)"""
5150
keyword_pattern = rf"{UTF8_PAYLOAD_PATTERN}\s+(?:like|LIKE|ilike|ILIKE)\s+{SINGLE_QUOTES_VALUE_PATTERN}"
52-
escape_manager = qradar_escape_manager
51+
escape_manager = aql_escape_manager
5352

5453
wildcard_symbol = "%"
5554

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from app.translator.platforms.carbonblack.renders.carbonblack_cti import CarbonBlackCTI
1+
from app.translator.platforms.carbonblack.renders.carbonblack_cti import CarbonBlackCTI # noqa: F401
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from app.translator.platforms.chronicle.parsers.chronicle import ChronicleQueryParser
2-
from app.translator.platforms.chronicle.parsers.chronicle_rule import ChronicleRuleParser
3-
from app.translator.platforms.chronicle.renders.chronicle import ChronicleQueryRender
4-
from app.translator.platforms.chronicle.renders.chronicle_cti import ChronicleQueryCTI
5-
from app.translator.platforms.chronicle.renders.chronicle_rule import ChronicleSecurityRuleRender
1+
from app.translator.platforms.chronicle.parsers.chronicle import ChronicleQueryParser # noqa: F401
2+
from app.translator.platforms.chronicle.parsers.chronicle_rule import ChronicleRuleParser # noqa: F401
3+
from app.translator.platforms.chronicle.renders.chronicle import ChronicleQueryRender # noqa: F401
4+
from app.translator.platforms.chronicle.renders.chronicle_cti import ChronicleQueryCTI # noqa: F401
5+
from app.translator.platforms.chronicle.renders.chronicle_rule import ChronicleSecurityRuleRender # noqa: F401
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from app.translator.platforms.crowdstrike.parsers.crowdstrike import CrowdStrikeQueryParser
2-
from app.translator.platforms.crowdstrike.renders.crowdstrike import CrowdStrikeQueryRender
3-
from app.translator.platforms.crowdstrike.renders.crowdstrike_cti import CrowdStrikeCTI
1+
from app.translator.platforms.crowdstrike.parsers.crowdstrike import CrowdStrikeQueryParser # noqa: F401
2+
from app.translator.platforms.crowdstrike.renders.crowdstrike import CrowdStrikeQueryRender # noqa: F401
3+
from app.translator.platforms.crowdstrike.renders.crowdstrike_cti import CrowdStrikeCTI # noqa: F401
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from app.translator.platforms.elasticsearch.parsers.detection_rule import ElasticSearchRuleParser
2-
from app.translator.platforms.elasticsearch.parsers.elasticsearch import ElasticSearchQueryParser
3-
from app.translator.platforms.elasticsearch.renders.detection_rule import ElasticSearchRuleRender
4-
from app.translator.platforms.elasticsearch.renders.elast_alert import ElastAlertRuleRender
5-
from app.translator.platforms.elasticsearch.renders.elasticsearch import ElasticSearchQueryRender
6-
from app.translator.platforms.elasticsearch.renders.elasticsearch_cti import ElasticsearchCTI
7-
from app.translator.platforms.elasticsearch.renders.kibana import KibanaRuleRender
8-
from app.translator.platforms.elasticsearch.renders.xpack_watcher import XPackWatcherRuleRender
1+
from app.translator.platforms.elasticsearch.parsers.detection_rule import ElasticSearchRuleParser # noqa: F401
2+
from app.translator.platforms.elasticsearch.parsers.elasticsearch import ElasticSearchQueryParser # noqa: F401
3+
from app.translator.platforms.elasticsearch.renders.detection_rule import ElasticSearchRuleRender # noqa: F401
4+
from app.translator.platforms.elasticsearch.renders.elast_alert import ElastAlertRuleRender # noqa: F401
5+
from app.translator.platforms.elasticsearch.renders.elasticsearch import ElasticSearchQueryRender # noqa: F401
6+
from app.translator.platforms.elasticsearch.renders.elasticsearch_cti import ElasticsearchCTI # noqa: F401
7+
from app.translator.platforms.elasticsearch.renders.kibana import KibanaRuleRender # noqa: F401
8+
from app.translator.platforms.elasticsearch.renders.xpack_watcher import XPackWatcherRuleRender # noqa: F401
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from app.translator.platforms.fireeye_helix.renders.fireeye_helix_cti import FireeyeHelixCTI
1+
from app.translator.platforms.fireeye_helix.renders.fireeye_helix_cti import FireeyeHelixCTI # noqa: F401
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from app.translator.platforms.forti_siem.renders.forti_siem_rule import FortiSiemRuleRender
1+
from app.translator.platforms.forti_siem.renders.forti_siem_rule import FortiSiemRuleRender # noqa: F401
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from app.translator.platforms.graylog.parsers.graylog import GraylogQueryParser
2-
from app.translator.platforms.graylog.renders.graylog import GraylogQueryRender
3-
from app.translator.platforms.graylog.renders.graylog_cti import GraylogCTI
1+
from app.translator.platforms.graylog.parsers.graylog import GraylogQueryParser # noqa: F401
2+
from app.translator.platforms.graylog.renders.graylog import GraylogQueryRender # noqa: F401
3+
from app.translator.platforms.graylog.renders.graylog_cti import GraylogCTI # noqa: F401
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from app.translator.platforms.logpoint.renders.logpoint_cti import LogpointCTI
1+
from app.translator.platforms.logpoint.renders.logpoint_cti import LogpointCTI # noqa: F401
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
from app.translator.platforms.logrhythm_axon.renders.logrhythm_axon_query import LogRhythmAxonQueryRender
2-
from app.translator.platforms.logrhythm_axon.renders.logrhythm_axon_rule import LogRhythmAxonRuleRender
1+
from app.translator.platforms.logrhythm_axon.renders.logrhythm_axon_query import LogRhythmAxonQueryRender # noqa: F401
2+
from app.translator.platforms.logrhythm_axon.renders.logrhythm_axon_rule import LogRhythmAxonRuleRender # noqa: F401
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from app.translator.platforms.logscale.parsers.logscale import LogScaleQueryParser
2-
from app.translator.platforms.logscale.parsers.logscale_alert import LogScaleAlertParser
3-
from app.translator.platforms.logscale.renders.logscale import LogScaleQueryRender
4-
from app.translator.platforms.logscale.renders.logscale_alert import LogScaleAlertRender
5-
from app.translator.platforms.logscale.renders.logscale_cti import LogScaleCTI
1+
from app.translator.platforms.logscale.parsers.logscale import LogScaleQueryParser # noqa: F401
2+
from app.translator.platforms.logscale.parsers.logscale_alert import LogScaleAlertParser # noqa: F401
3+
from app.translator.platforms.logscale.renders.logscale import LogScaleQueryRender # noqa: F401
4+
from app.translator.platforms.logscale.renders.logscale_alert import LogScaleAlertRender # noqa: F401
5+
from app.translator.platforms.logscale.renders.logscale_cti import LogScaleCTI # noqa: F401

0 commit comments

Comments
 (0)