From fcda4e65cd5095a7bc63a11d29e5b77dba217274 Mon Sep 17 00:00:00 2001 From: Syd Pleno Date: Thu, 19 Sep 2024 07:15:02 +1000 Subject: [PATCH 1/9] Update TimelineSearch --- .../src/components/Analyzer/TimelineSearch.vue | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/timesketch/frontend-ng/src/components/Analyzer/TimelineSearch.vue b/timesketch/frontend-ng/src/components/Analyzer/TimelineSearch.vue index dd35c05367..f0786bf321 100644 --- a/timesketch/frontend-ng/src/components/Analyzer/TimelineSearch.vue +++ b/timesketch/frontend-ng/src/components/Analyzer/TimelineSearch.vue @@ -92,8 +92,17 @@ export default { }, analyzerTimelineId: { handler: function (id) { - if (id) this.selectedTimelines.push(id) - if (!id) this.selectedTimelines = [] + if (Array.isArray(id)) { + this.selectedTimelines = id + } else { + if (id) { + this.selectedTimelines.push(id) + } + else { + this.selectedTimelines = [] + } + } + }, }, }, From e19a589f77ec4ece471d93366e8b8bf73c69edf7 Mon Sep 17 00:00:00 2001 From: Syd Pleno Date: Thu, 19 Sep 2024 07:16:41 +1000 Subject: [PATCH 2/9] Add event selection to VisualizationEditor --- .../Visualization/AggregationEventSelect.vue | 205 ++++++++++++++++++ .../Visualization/VisualizationEditor.vue | 37 ++-- 2 files changed, 221 insertions(+), 21 deletions(-) create mode 100644 timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue diff --git a/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue b/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue new file mode 100644 index 0000000000..d3f274f0e5 --- /dev/null +++ b/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue @@ -0,0 +1,205 @@ + + + + diff --git a/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue b/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue index 111e6bebcd..272f19dea0 100644 --- a/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue +++ b/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue @@ -36,19 +36,18 @@ limitations under the License. - - - - - - - + + 0 && this.selectedQueryString + }, sketch() { return this.$store.state.sketch }, @@ -304,13 +306,6 @@ export default { } return undefined }, - currentQueryString() { - const currentSearchNode = this.$store.state.currentSearchNode - if (!currentSearchNode) { - return "" - } - return currentSearchNode.query_string - }, }, methods: { rename() { From 46f42874eb182c85e4dc2d5bc17b18e41744e4dc Mon Sep 17 00:00:00 2001 From: Syd Pleno Date: Fri, 20 Sep 2024 05:29:17 +0000 Subject: [PATCH 3/9] Add placeholder image --- .../frontend-ng/public/vis_placeholder.png | Bin 0 -> 6651 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 timesketch/frontend-ng/public/vis_placeholder.png diff --git a/timesketch/frontend-ng/public/vis_placeholder.png b/timesketch/frontend-ng/public/vis_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..bbcdd1e865189690e837aebc6b2f8a4e9de341a1 GIT binary patch literal 6651 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 literal 0 HcmV?d00001 From 43752dcdb274ed8ed20f964c506d9c308e51fb6f Mon Sep 17 00:00:00 2001 From: Syd Pleno Date: Sat, 21 Sep 2024 10:12:28 +1000 Subject: [PATCH 4/9] Add placeholder image --- .../frontend-ng/dist/vis_placeholder.png | Bin 0 -> 6651 bytes .../Visualization/AggregationEventSelect.vue | 24 +++++++++--------- .../Visualization/VisualizationEditor.vue | 6 +++++ 3 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 timesketch/frontend-ng/dist/vis_placeholder.png diff --git a/timesketch/frontend-ng/dist/vis_placeholder.png b/timesketch/frontend-ng/dist/vis_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..bbcdd1e865189690e837aebc6b2f8a4e9de341a1 GIT binary patch literal 6651 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 literal 0 HcmV?d00001 diff --git a/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue b/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue index d3f274f0e5..a40de31ff0 100644 --- a/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue +++ b/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue @@ -34,36 +34,36 @@ limitations under the License. autofocus label="Event query" v-model="selectedQueryString" - > - - + - - + - + diff --git a/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue b/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue index 272f19dea0..dac8c845da 100644 --- a/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue +++ b/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue @@ -86,6 +86,12 @@ limitations under the License. > + Date: Sat, 21 Sep 2024 11:03:55 +1000 Subject: [PATCH 5/9] Fix type --- .../src/components/Visualization/AggregationEventSelect.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue b/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue index a40de31ff0..1cb4d14ea7 100644 --- a/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue +++ b/timesketch/frontend-ng/src/components/Visualization/AggregationEventSelect.vue @@ -81,7 +81,7 @@ export default { type: String }, queryChips: { - type: Object + type: Array }, recentSearch: { type: Object, From 3a26d28f179721b7560ec8ac55cd3c83ffd33fcc Mon Sep 17 00:00:00 2001 From: Syd Pleno Date: Sun, 22 Sep 2024 19:33:13 +1000 Subject: [PATCH 6/9] Update aggregator --- timesketch/lib/aggregators/apex.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/timesketch/lib/aggregators/apex.py b/timesketch/lib/aggregators/apex.py index dc6e4d19e7..eff76c1834 100644 --- a/timesketch/lib/aggregators/apex.py +++ b/timesketch/lib/aggregators/apex.py @@ -137,7 +137,8 @@ def add_match_phrase_filter(self, field, value, clause="must"): if clause not in self._VALID_QUERY_CLAUSES: raise ValueError(f"Unknown boolean clause {clause}") - + if isinstance(value, str): + field = f"{field}.keyword" self.bool_queries[clause].append({"match_phrase": {field: {"query": value}}}) def add_term_filter(self, field, value, clause="filter", term_type="term"): @@ -409,7 +410,7 @@ def _build_aggregation_query_spec(self, aggregator_options): elif chip_type == "datetime_range": aggregation_query.add_datetime_range(chip_value, chip_operator) elif chip_type == "term": - aggregation_query.add_term_filter( + aggregation_query.add_match_phrase_filter( chip_field, chip_value, chip_operator ) else: @@ -418,7 +419,6 @@ def _build_aggregation_query_spec(self, aggregator_options): aggregation_query.aggregation_query = self._get_aggregation_dsl( aggregator_options ) - return aggregation_query.spec def run( From dcf8edbed55c35d7f6e9bae14731ea6994412fbc Mon Sep 17 00:00:00 2001 From: janosch Date: Tue, 8 Oct 2024 22:18:15 +0000 Subject: [PATCH 7/9] * Adding an API endpoint to get fields per timeline. * Provide only fields that exist in all selected timelines for the aggregation. * Minor UI fix for the EventList * Dynamically sized the placeholder picture --- timesketch/api/v1/resources/timeline.py | 98 +++++++++++++++++++ timesketch/api/v1/routes.py | 5 + .../src/components/Explore/EventList.vue | 10 +- .../Visualization/AggregationConfig.vue | 10 ++ .../Visualization/EventFieldSelect.vue | 55 ++++++----- .../Visualization/VisualizationEditor.vue | 59 ++++++++++- .../frontend-ng/src/utils/RestApiClient.js | 3 + 7 files changed, 206 insertions(+), 34 deletions(-) diff --git a/timesketch/api/v1/resources/timeline.py b/timesketch/api/v1/resources/timeline.py index 78dfc38f57..9d1425bc0e 100644 --- a/timesketch/api/v1/resources/timeline.py +++ b/timesketch/api/v1/resources/timeline.py @@ -20,6 +20,7 @@ import six import opensearchpy +from flask import jsonify from flask import request from flask import abort from flask import current_app @@ -40,6 +41,7 @@ from timesketch.models.sketch import SearchIndex from timesketch.models.sketch import Sketch from timesketch.models.sketch import Timeline +from timesketch.lib.aggregators import manager as aggregator_manager logger = logging.getLogger("timesketch.timeline_api") @@ -531,3 +533,99 @@ def post(self): utils.update_sketch_last_activity(sketch) return self.to_json(searchindex, status_code=HTTP_STATUS_CODE_CREATED) + + +class TimelineFieldsResource(resources.ResourceMixin, Resource): + """Resource to retrieve unique fields present in a timeline. + + This resource aggregates data types within a timeline and then queries + OpenSearch to retrieve all unique fields present across those data types, + excluding default Timesketch fields. + """ + + @login_required + def get(self, sketch_id, timeline_id): + """Handles GET request to retrieve unique fields in a timeline. + + Args: + sketch_id (int): The ID of the sketch. + timeline_id (int): The ID of the timeline. + + Returns: + flask.wrappers.Response: A JSON response containing a list of + unique fields in the timeline, sorted alphabetically. Returns + an empty list if no fields are found or if there's an error. + Possible error codes: 400, 403, 404. + """ + + sketch = Sketch.get_with_acl(sketch_id) + if not sketch: + abort(HTTP_STATUS_CODE_NOT_FOUND, "No sketch found with this ID.") + if not sketch.has_permission(current_user, "read"): + abort( + HTTP_STATUS_CODE_FORBIDDEN, + "User does not have read access controls on sketch.", + ) + + timeline = Timeline.get_by_id(timeline_id) + if not timeline: + abort(HTTP_STATUS_CODE_NOT_FOUND, "No timeline found with this ID.") + + # Check that this timeline belongs to the sketch + if timeline.sketch.id != sketch.id: + abort( + HTTP_STATUS_CODE_NOT_FOUND, + "The timeline does not belong to the sketch.", + ) + + index_name = timeline.searchindex.index_name + timeline_fields = set() + + # 1. Get distinct data types for the timeline using aggregation + aggregator_name = "field_bucket" + aggregator_parameters = { + "field": "data_type", + "limit": "10000", # Get all data types + } + + agg_class = aggregator_manager.AggregatorManager.get_aggregator(aggregator_name) + if not agg_class: + abort(HTTP_STATUS_CODE_NOT_FOUND, f"Aggregator {aggregator_name} not found") + + aggregator = agg_class( + sketch_id=sketch_id, indices=[index_name], timeline_ids=[timeline_id] + ) + result_obj = aggregator.run(**aggregator_parameters) + + if not result_obj: + abort(HTTP_STATUS_CODE_BAD_REQUEST, "Error running data type aggregation.") + + data_types = sorted([bucket["data_type"] for bucket in result_obj.values]) + + # 2. For each data type, query for a single event to get fields + for data_type in data_types: + query_filter = {"indices": [timeline_id], "size": 1} + + try: + result = self.datastore.search( + sketch_id=sketch_id, + query_string=f'data_type:"{data_type}"', + query_filter=query_filter, + query_dsl=None, + indices=[index_name], + timeline_ids=[timeline_id], + ) + except ValueError as e: + abort(HTTP_STATUS_CODE_BAD_REQUEST, str(e)) + + if isinstance(result, dict) and result.get("hits", {}).get("hits", []): + event = result["hits"]["hits"][0]["_source"] + for field in event: + if field not in [ + "datetime", + "timestamp", + "__ts_timeline_id", + ]: + timeline_fields.add(field) + + return jsonify({"objects": sorted(list(timeline_fields))}) diff --git a/timesketch/api/v1/routes.py b/timesketch/api/v1/routes.py index 9846d90a7c..2e07fc47bc 100644 --- a/timesketch/api/v1/routes.py +++ b/timesketch/api/v1/routes.py @@ -56,6 +56,7 @@ from .resources.explore import QueryResource from .resources.timeline import TimelineResource from .resources.timeline import TimelineListResource +from timesketch.api.v1.resources.timeline import TimelineFieldsResource from .resources.searchindex import SearchIndexListResource from .resources.searchindex import SearchIndexResource from .resources.session import SessionResource @@ -167,6 +168,10 @@ TimelineResource, "/sketches//timelines//", ), + ( + TimelineFieldsResource, + "/sketches//timelines//fields/", + ), (SearchIndexListResource, "/searchindices/"), (SearchIndexResource, "/searchindices//"), ( diff --git a/timesketch/frontend-ng/src/components/Explore/EventList.vue b/timesketch/frontend-ng/src/components/Explore/EventList.vue index d957f27c3a..9d6a9f5678 100644 --- a/timesketch/frontend-ng/src/components/Explore/EventList.vue +++ b/timesketch/frontend-ng/src/components/Explore/EventList.vue @@ -30,12 +30,10 @@ limitations under the License.

diff --git a/timesketch/frontend-ng/src/components/Visualization/AggregationConfig.vue b/timesketch/frontend-ng/src/components/Visualization/AggregationConfig.vue index 3fb73e778a..24b26904f5 100644 --- a/timesketch/frontend-ng/src/components/Visualization/AggregationConfig.vue +++ b/timesketch/frontend-ng/src/components/Visualization/AggregationConfig.vue @@ -20,6 +20,8 @@ limitations under the License. @@ -114,6 +116,14 @@ export default { splitByTimeline: { type: Boolean, }, + timelineFields: { + type: Array, + default: () => [], + }, + loadingFields: { + type: Boolean, + default: false + }, }, data() { return { diff --git a/timesketch/frontend-ng/src/components/Visualization/EventFieldSelect.vue b/timesketch/frontend-ng/src/components/Visualization/EventFieldSelect.vue index a6976e2bb1..bf4694c915 100644 --- a/timesketch/frontend-ng/src/components/Visualization/EventFieldSelect.vue +++ b/timesketch/frontend-ng/src/components/Visualization/EventFieldSelect.vue @@ -17,19 +17,20 @@ limitations under the License. - - @@ -56,34 +57,40 @@ export default { field: { type: Object, }, + timelineFields: { + type: Array, + default: () => [], + }, + loadingFields: { + type: Boolean, + default: false + }, }, data() { return { selectedField: this.field, - } }, computed: { - allNonTimestampFields() { - let mappings = this.$store.state.meta.mappings - .filter( - (mapping) => { - return ( - mapping['field'] !== 'datetime' - && mapping['field'] !== 'timestamp' - ) - }) - .map( - (mapping) => { - return {text: mapping['field'], value: mapping}} - ) - return mappings + mappedTimelineFields() { + const mappings = this.$store.state.meta.mappings; + + return this.timelineFields.map(field => { + const mapping = mappings.find(m => m.field === field); + const type = mapping ? mapping.type : 'unknown'; + return { text: field, value: { field, type } }; + }); }, }, watch: { field() { this.selectedField = this.field - } + }, + timelineFields(newFields) { + if (!newFields || newFields.length === 0) { + this.selectedField = null; + } + }, } } diff --git a/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue b/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue index dac8c845da..49b3ee1d4b 100644 --- a/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue +++ b/timesketch/frontend-ng/src/components/Visualization/VisualizationEditor.vue @@ -39,7 +39,7 @@ limitations under the License. - + { + this.availableTimelineFields = response.data.objects + this.loadingFields = false + }) + .catch(error => { + console.error("Error fetching fields:", error); + this.availableTimelineFields = []; + this.loadingFields = false + }); + } + else { + const promises = timelineIDs.map(timelineID => { + return ApiClient.getTimelineFields(this.sketch.id, timelineID) + .then(response => response.data.objects) + .catch(error => { + console.error("Error fetching timeline fields:", error); + return [] + }) + }) + + Promise.all(promises) + .then(results => { + // Flatten the arrays and create a set to guarantee uniqueness + const intersection = results.reduce((a, b) => a.filter(c => b.includes(c))); + this.availableTimelineFields = intersection + this.loadingFields = false; + }) + .catch(error => { + console.error("Error in Promise.all", error) + this.availableTimelineFields = [] + this.loadingFields = false; + }) + } + }, rename() { this.renameVisualDialog = false this.selectedChartTitle = this.selectedChartTitleDraft diff --git a/timesketch/frontend-ng/src/utils/RestApiClient.js b/timesketch/frontend-ng/src/utils/RestApiClient.js index 3fe3809479..b514354d8b 100644 --- a/timesketch/frontend-ng/src/utils/RestApiClient.js +++ b/timesketch/frontend-ng/src/utils/RestApiClient.js @@ -111,6 +111,9 @@ export default { getSketchTimelineAnalysis(sketchId, timelineId) { return RestApiClient.get('/sketches/' + sketchId + '/timelines/' + timelineId + '/analysis/') }, + getTimelineFields(sketchId, timelineId){ + return RestApiClient.get('/sketches/' + sketchId + '/timelines/' + timelineId + '/fields/') + }, saveSketchTimeline(sketchId, timelineId, name, description, color) { let formData = { name: name, From a3e3562bc8f898167d33bf8deac71e69a31ed6c4 Mon Sep 17 00:00:00 2001 From: janosch Date: Tue, 8 Oct 2024 22:24:56 +0000 Subject: [PATCH 8/9] linter --- timesketch/api/v1/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timesketch/api/v1/routes.py b/timesketch/api/v1/routes.py index 2e07fc47bc..0993110caa 100644 --- a/timesketch/api/v1/routes.py +++ b/timesketch/api/v1/routes.py @@ -56,7 +56,7 @@ from .resources.explore import QueryResource from .resources.timeline import TimelineResource from .resources.timeline import TimelineListResource -from timesketch.api.v1.resources.timeline import TimelineFieldsResource +from .resources.timeline import TimelineFieldsResource from .resources.searchindex import SearchIndexListResource from .resources.searchindex import SearchIndexResource from .resources.session import SessionResource From 0b31ca71714fb730052c1f577ea73b69c91bade6 Mon Sep 17 00:00:00 2001 From: janosch Date: Wed, 9 Oct 2024 15:28:21 +0000 Subject: [PATCH 9/9] Add todo. --- timesketch/api/v1/resources/timeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/timesketch/api/v1/resources/timeline.py b/timesketch/api/v1/resources/timeline.py index 9d1425bc0e..3c81023d44 100644 --- a/timesketch/api/v1/resources/timeline.py +++ b/timesketch/api/v1/resources/timeline.py @@ -535,6 +535,7 @@ def post(self): return self.to_json(searchindex, status_code=HTTP_STATUS_CODE_CREATED) +# TODO(Issue 3200): Research more efficient ways to gather unique fields. class TimelineFieldsResource(resources.ResourceMixin, Resource): """Resource to retrieve unique fields present in a timeline.