From 8154c09c85a625bb26af019b4108d83ab48f3af7 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Thu, 15 Dec 2022 13:43:29 +0100 Subject: [PATCH 1/2] [FEATURE] Add interactive legend into charts #199 Signed-off-by: Jovan Cvetkovic --- public/pages/Overview/utils/helpers.ts | 83 +++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/public/pages/Overview/utils/helpers.ts b/public/pages/Overview/utils/helpers.ts index e26d52d52..da2cd4332 100644 --- a/public/pages/Overview/utils/helpers.ts +++ b/public/pages/Overview/utils/helpers.ts @@ -16,6 +16,32 @@ export type DateOpts = { dateFormat: string; }; +/** + * Legend selection config for the chart layer + */ +const legendSelectionCfg = { + selection: { + series: { + type: 'multi', + encodings: ['color'], + on: 'click', + bind: 'legend', + }, + }, + encoding: { + opacity: { + condition: { selection: 'series', value: 1 }, + value: 0.2, + }, + }, +}; + +/** + * Adds interactive legends to the chart layer + * @param layer + */ +const addInteractiveLegends = (layer: any) => _.defaultsDeep(layer, legendSelectionCfg); + function getVisualizationSpec(description: string, data: any, layers: any[]): TopLevelSpec { return { config: { @@ -58,28 +84,42 @@ export function getOverviewVisualizationSpec( title: 'Count', axis: { grid: true, ticks: false }, }, + tooltip: [{ field: 'finding', aggregate: 'sum', type: 'quantitative', title: 'Findings' }], }; - if (groupBy === 'log_type') { - findingsEncoding['color'] = { field: 'logType', type: 'nominal', title: 'Log type' }; + if (groupBy === 'logType') { + findingsEncoding['color'] = { + field: 'logType', + type: 'nominal', + title: 'Log type', + scale: { + range: euiPaletteColorBlind(), + }, + }; } + const lineColor = '#ff0000'; return getVisualizationSpec( 'Plot showing average data with raw values in the background.', visualizationData, [ - { + addInteractiveLegends({ mark: 'bar', encoding: findingsEncoding, - }, + }), { mark: { type: 'line', - color: '#ff0000', + color: lineColor, + point: { + filled: true, + fill: lineColor, + }, }, encoding: { x: { timeUnit, field: 'time', title: '', axis: { grid: false, ticks: false } }, y: { aggregate, field: 'alert', title: 'Count', axis: { grid: true, ticks: false } }, + tooltip: [{ field: 'alert', aggregate: 'sum', title: 'Alerts' }], }, }, ] @@ -132,9 +172,10 @@ export function getFindingsVisualizationSpec( } ) { return getVisualizationSpec('Findings data overview', visualizationData, [ - { + addInteractiveLegends({ mark: 'bar', encoding: { + tooltip: [{ field: 'finding', aggregate: 'sum', type: 'quantitative', title: 'Findings' }], x: { timeUnit: dateOpts.timeUnit, field: 'time', @@ -160,7 +201,7 @@ export function getFindingsVisualizationSpec( }, }, }, - }, + }), ]); } @@ -173,9 +214,10 @@ export function getAlertsVisualizationSpec( } ) { return getVisualizationSpec('Alerts data overview', visualizationData, [ - { + addInteractiveLegends({ mark: 'bar', encoding: { + tooltip: [{ field: 'alert', aggregate: 'sum', title: 'Alerts' }], x: { timeUnit: dateOpts.timeUnit, field: 'time', @@ -202,7 +244,7 @@ export function getAlertsVisualizationSpec( }, }, }, - }, + }), ]); } @@ -210,7 +252,30 @@ export function getTopRulesVisualizationSpec(visualizationData: any[]) { return getVisualizationSpec('Most frequent detection rules', visualizationData, [ { mark: { type: 'arc', innerRadius: 90 }, + transform: [ + { + joinaggregate: [ + { + op: 'sum', + field: 'count', + as: 'total', + }, + ], + }, + { + calculate: 'datum.count/datum.total', + as: 'percentage', + }, + ], encoding: { + tooltip: [ + { + field: 'percentage', + title: 'Percentage', + type: 'quantitative', + format: '2.0%', + }, + ], theta: { aggregate: 'sum', field: 'count', type: 'quantitative' }, color: { field: 'ruleName', From b52555d342242d5ca529b7f29158d4773ea99935 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Mon, 19 Dec 2022 22:37:28 +0100 Subject: [PATCH 2/2] Fix cypress test Signed-off-by: Jovan Cvetkovic --- cypress/integration/4_findings.spec.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cypress/integration/4_findings.spec.js b/cypress/integration/4_findings.spec.js index eb51842a9..6eedc94f9 100644 --- a/cypress/integration/4_findings.spec.js +++ b/cypress/integration/4_findings.spec.js @@ -135,14 +135,21 @@ describe('Findings', () => { // Click on detector to be removed cy.contains('sample_detector').click({ force: true }, { timeout: 2000 }); - // Click "Actions" button, the click "Delete" - cy.get('button').contains('Actions').click({ force: true }, { timeout: 2000 }); - cy.contains('Delete').click({ force: true }); - - // Search for sample_detector, presumably deleted - cy.get(`[placeholder="Search threat detectors"]`).type('sample_detector').trigger('search'); + cy.url().should('include', 'opensearch_security_analytics_dashboards#/detector-details'); - // Confirm sample_detector no longer exists - cy.contains('There are no existing detectors.'); + // Click "Actions" button, the click "Delete" + cy.get('button') + .contains('Actions') + .click({ force: true }, { timeout: 2000 }) + .then(() => { + // Confirm arrival at detectors page + cy.contains('Delete').click({ force: true }); + + // Search for sample_detector, presumably deleted + cy.get(`[placeholder="Search threat detectors"]`).type('sample_detector').trigger('search'); + + // Confirm sample_detector no longer exists + cy.contains('There are no existing detectors.'); + }); }); });