From 7180f8ac0c662966536053af9bdb47ccd4fbd9ff Mon Sep 17 00:00:00 2001 From: AWSHurneyt Date: Thu, 21 Apr 2022 09:55:07 -0700 Subject: [PATCH] Fixed bugs associated with alerts table, and addressed UX review feedback. (#222) * Removed unsupported prop from EuiGrid. Signed-off-by: AWSHurneyt * Increased limit of doc level trigger conditions. Signed-off-by: AWSHurneyt * Refactored empty dashboard button based on UX feedback. Signed-off-by: AWSHurneyt * Refactored query performance text based on UX feedback. Signed-off-by: AWSHurneyt * Refactored logic to apply default sortField value for the getAlerts API. Signed-off-by: AWSHurneyt * Refactored logic to apply default sortField value for the getFindings API. Signed-off-by: AWSHurneyt * Refactored alerts table to display finding doc IDs. Fixed a bug that was preventing the alerts flyout table from refreshing after acknowledging alerts. Removed edit monitor button from alerts table on monitor details page. Fixed a bug that was causing the monitor create/update page to crash when changing from extraction editor to visual editor. Refactored position of finding flyout. Fixed a bug that was preventing sorting and pagination of the findings table. Repurposed QueryPopover to FindingsPopover, and added support for displaying doc IDs using the popover. Removed parentheses from the trigger condition sent to backend. Added validation for queries defined using the visual editor. Signed-off-by: AWSHurneyt * Updated snapshots, and implemented fix for a unit test. Signed-off-by: AWSHurneyt --- .../AlertsDashboardFlyoutComponent.js | 28 +++++-- .../components/MonitorType/MonitorType.js | 2 +- .../__snapshots__/MonitorType.test.js.snap | 1 - .../QueryPerformance/QueryPerformance.js | 4 +- .../QueryPerformance.test.js.snap | 4 +- .../containers/DefineMonitor/DefineMonitor.js | 56 ++++++++----- .../CreateTrigger/utils/formikToTrigger.js | 2 +- .../DefineDocumentLevelTrigger.js | 2 +- .../DashboardEmptyPrompt.js | 15 ++-- .../FindingsDashboard/FindingFlyout.js | 13 ++- .../{QueriesPopover.js => FindingsPopover.js} | 32 +++++-- .../components/FindingsDashboard/utils.js | 83 +++++++++++++------ .../Dashboard/containers/FindingsDashboard.js | 83 ++++++++++++++++--- server/services/AlertService.js | 17 ++-- server/services/FindingService.js | 11 +-- 15 files changed, 245 insertions(+), 108 deletions(-) rename public/pages/Dashboard/components/FindingsDashboard/{QueriesPopover.js => FindingsPopover.js} (50%) diff --git a/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js index 730e2b535..cc695f837 100644 --- a/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js +++ b/public/components/Flyout/flyouts/components/AlertsDashboardFlyoutComponent.js @@ -47,7 +47,10 @@ import { DEFAULT_PAGE_SIZE_OPTIONS } from '../../../../pages/Monitors/containers import queryString from 'query-string'; import { MAX_ALERT_COUNT } from '../../../../pages/Dashboard/utils/constants'; import { SEVERITY_OPTIONS } from '../../../../pages/CreateTrigger/utils/constants'; -import { TABLE_TAB_IDS } from '../../../../pages/Dashboard/components/FindingsDashboard/utils'; +import { + ALERTS_FINDING_COLUMN, + TABLE_TAB_IDS, +} from '../../../../pages/Dashboard/components/FindingsDashboard/utils'; import FindingsDashboard from '../../../../pages/Dashboard/containers/FindingsDashboard'; export const DEFAULT_NUM_FLYOUT_ROWS = 10; @@ -146,11 +149,15 @@ export default class AlertsDashboardFlyoutComponent extends Component { } getBucketLevelGraphConditions = (trigger) => { - let conditions = _.get(trigger, 'condition.script.source', '-'); - conditions = _.replace(conditions, ' && ', '&AND&'); - conditions = _.replace(conditions, ' || ', '&OR&'); - conditions = conditions.split(/&/); - return conditions.join('\n'); + let conditions = _.get(trigger, 'condition.script.source'); + if (_.isEmpty(conditions)) { + return '-'; + } else { + conditions = conditions.replaceAll(' && ', '&AND&'); + conditions = conditions.replaceAll(' || ', '&OR&'); + conditions = conditions.split(/&/); + return conditions.join('\n'); + } }; getSeverityText = (severity) => { @@ -167,7 +174,7 @@ export default class AlertsDashboardFlyoutComponent extends Component { }; getAlerts = async () => { - this.setState({ loading: true }); + this.setState({ loading: true, tabContent: undefined }); const { from, search, @@ -263,8 +270,7 @@ export default class AlertsDashboardFlyoutComponent extends Component { alertState, monitorIds ); - this.setState({ selectedItems: [], tabContent: undefined }); - this.setState({ tabContent: this.renderAlertsTable() }); + this.setState({ selectedItems: [] }); this.props.refreshDashboard(); }; @@ -349,6 +355,10 @@ export default class AlertsDashboardFlyoutComponent extends Component { case MONITOR_TYPE.BUCKET_LEVEL: columns = insertGroupByColumn(groupBy); break; + case MONITOR_TYPE.DOC_LEVEL: + columns = _.cloneDeep(queryColumns); + columns.splice(0, 0, ALERTS_FINDING_COLUMN); + break; default: columns = queryColumns; break; diff --git a/public/pages/CreateMonitor/components/MonitorType/MonitorType.js b/public/pages/CreateMonitor/components/MonitorType/MonitorType.js index 03a8a39d9..983b3472f 100644 --- a/public/pages/CreateMonitor/components/MonitorType/MonitorType.js +++ b/public/pages/CreateMonitor/components/MonitorType/MonitorType.js @@ -60,7 +60,7 @@ const documentLevelDescription = ( // TODO DRAFT: confirm wording ); const MonitorType = ({ values }) => ( - +
( ( - Query duration + Monitor duration {`${_.get(response, 'took', DEFAULT_EMPTY_DATA)} ms`} diff --git a/public/pages/CreateMonitor/components/QueryPerformance/__snapshots__/QueryPerformance.test.js.snap b/public/pages/CreateMonitor/components/QueryPerformance/__snapshots__/QueryPerformance.test.js.snap index 6e89b5f99..e0117d4f0 100644 --- a/public/pages/CreateMonitor/components/QueryPerformance/__snapshots__/QueryPerformance.test.js.snap +++ b/public/pages/CreateMonitor/components/QueryPerformance/__snapshots__/QueryPerformance.test.js.snap @@ -15,7 +15,7 @@ exports[`QueryPerformance renders 1`] = `

- Query performance + Monitor performance

- Query duration + Monitor duration -
{message}
+
{_.isEmpty(message) ? : message}
); @@ -236,14 +237,18 @@ class DefineMonitor extends Component { } }; + let accordionTitle = 'Preview query and performance'; const previewContent = () => { switch (values.monitor_type) { case MONITOR_TYPE.BUCKET_LEVEL: return this.getBucketMonitorGraphs(aggregations, formikSnapshot, response); case MONITOR_TYPE.DOC_LEVEL: const { index, queries } = values; + accordionTitle = 'Preview findings and performance'; return _.isEmpty(response) ? ( - renderEmptyMessage('Loading findings...') + renderEmptyMessage( + validDocLevelGraphQueries(queries) ? '' : 'You must define at least one query.' + ) ) : ( - + @@ -286,9 +288,16 @@ class DefineMonitor extends Component { async onRunQuery() { this.setState({ loadingResponse: true }); const { httpClient, values, notifications } = this.props; - const formikSnapshot = _.cloneDeep(values); + const { monitor_type, searchType } = values; + + // Cancel execution criteria + switch (monitor_type) { + case MONITOR_TYPE.DOC_LEVEL: + const { queries } = values; + if (SEARCH_TYPE.GRAPH && !validDocLevelGraphQueries(queries)) return; + } - const searchType = values.searchType; + const formikSnapshot = _.cloneDeep(values); let requests; switch (searchType) { case SEARCH_TYPE.QUERY: @@ -341,20 +350,29 @@ class DefineMonitor extends Component { const endTime = moment(); const duration = moment.duration(endTime.diff(startTime)).milliseconds(); const response = _.get(queryResponse.resp, 'input_results.results[0]'); + // If there is an optionalResponse use it's results, otherwise use the original response const performanceResponse = optionalResponse ? _.get(optionalResponse, 'resp.input_results.results[0]', null) : response; - this.setState({ - response, - formikSnapshot, - // TODO FIXME: Doc level backend monitor run results don't include duration metric. Using this for now. - // This returns a much longer duration than other monitors, though. - performanceResponse: - values.monitor_type === MONITOR_TYPE.DOC_LEVEL - ? { ...performanceResponse, took: duration } - : performanceResponse, - }); + this.setState({ response, formikSnapshot, performanceResponse }); + + // TODO FIXME: Doc level backend monitor run results don't include duration metrics. Using this for now. + // This returns a much longer duration than other monitors, though. + if (monitor_type === MONITOR_TYPE.DOC_LEVEL) { + let hitsCount = 0; + _.keys(response).forEach( + (resultKey) => (hitsCount += _.values(performanceResponse[resultKey]).length) + ); + this.setState({ + performanceResponse: { + ...performanceResponse, + took: duration, + invalid: { path: duration }, + hits: { total: { value: hitsCount } }, + }, + }); + } } else { console.error('There was an error running the query', queryResponse.resp); backendErrorNotification(notifications, 'run', 'query', queryResponse.resp); diff --git a/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js b/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js index 51fe4d72a..e84e30ecc 100644 --- a/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js +++ b/public/pages/CreateTrigger/containers/CreateTrigger/utils/formikToTrigger.js @@ -109,7 +109,7 @@ export function getDocumentLevelScriptSource(conditions) { if (!_.isEmpty(query) && !_.isEmpty(query.queryName)) { const queryExpression = _.get(query, 'expression'); const operator = query.operator === '!=' ? '!' : ''; - scriptSourceContents.push(`(${operator}query[${queryExpression}])`); + scriptSourceContents.push(`${operator}query[${queryExpression}]`); } }); return scriptSourceContents.join(' '); diff --git a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js index 6d0a932e4..7b749fd5a 100644 --- a/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js +++ b/public/pages/CreateTrigger/containers/DefineDocumentLevelTrigger/DefineDocumentLevelTrigger.js @@ -31,7 +31,7 @@ import { backendErrorNotification, inputLimitText } from '../../../../utils/help import monitorToFormik from '../../../CreateMonitor/containers/CreateMonitor/utils/monitorToFormik'; import { buildRequest } from '../../../CreateMonitor/containers/DefineMonitor/utils/searchRequests'; -const MAX_TRIGGER_CONDITIONS = 5; // TODO DRAFT: Placeholder limit +const MAX_TRIGGER_CONDITIONS = 10; const defaultRowProps = { label: 'Trigger name', diff --git a/public/pages/Dashboard/components/DashboardEmptyPrompt/DashboardEmptyPrompt.js b/public/pages/Dashboard/components/DashboardEmptyPrompt/DashboardEmptyPrompt.js index c1ca17d80..ebc7b35c9 100644 --- a/public/pages/Dashboard/components/DashboardEmptyPrompt/DashboardEmptyPrompt.js +++ b/public/pages/Dashboard/components/DashboardEmptyPrompt/DashboardEmptyPrompt.js @@ -5,7 +5,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiButton, EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import { EuiButton, EuiButtonEmpty, EuiEmptyPrompt, EuiText } from '@elastic/eui'; import { APP_PATH } from '../../../../utils/constants'; import { PLUGIN_NAME } from '../../../../../utils/constants'; @@ -23,9 +23,7 @@ const createMonitorButton = ( ); const editMonitorButton = (onCreateTrigger) => ( - - Edit monitor - + Edit monitor ); const DashboardEmptyPrompt = ({ onCreateTrigger, isModal = false }) => { @@ -35,6 +33,11 @@ const DashboardEmptyPrompt = ({ onCreateTrigger, isModal = false }) => { : inMonitorDetails ? createTriggerText : createMonitorText; + const actions = inMonitorDetails + ? undefined + : isModal + ? editMonitorButton(onCreateTrigger) + : createMonitorButton; return ( {

{displayText}

} - actions={ - inMonitorDetails || isModal ? editMonitorButton(onCreateTrigger) : createMonitorButton - } + actions={actions} /> ); }; diff --git a/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js index 331be736e..b2e55e92c 100644 --- a/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js +++ b/public/pages/Dashboard/components/FindingsDashboard/FindingFlyout.js @@ -64,8 +64,8 @@ export default class FindingFlyout extends Component { onClose={this.closeFlyout} ownFocus={false} hideCloseButton={true} - side={isAlertsFlyout ? 'left' : 'right'} - size={'s'} + side={'right'} + size={'m'} > @@ -119,13 +119,20 @@ export default class FindingFlyout extends Component { overflowHeight={600} inline={false} isCopyable + style={{ height: '400px' }} > {JSON.stringify(documentDisplay, null, 3)} - Close + + Close + ); diff --git a/public/pages/Dashboard/components/FindingsDashboard/QueriesPopover.js b/public/pages/Dashboard/components/FindingsDashboard/FindingsPopover.js similarity index 50% rename from public/pages/Dashboard/components/FindingsDashboard/QueriesPopover.js rename to public/pages/Dashboard/components/FindingsDashboard/FindingsPopover.js index ed2ad4f9b..4da4670fc 100644 --- a/public/pages/Dashboard/components/FindingsDashboard/QueriesPopover.js +++ b/public/pages/Dashboard/components/FindingsDashboard/FindingsPopover.js @@ -4,16 +4,33 @@ */ import React, { useState } from 'react'; +import _ from 'lodash'; import { EuiLink, EuiPopover, EuiSpacer, EuiText } from '@elastic/eui'; -export default function QueryPopover(queries) { +export default function FindingsPopover({ docIds = [], queries = [] }) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const onButtonClick = () => setIsPopoverOpen(!isPopoverOpen); const closePopover = () => setIsPopoverOpen(false); - const popoverContent = queries.queries.map((query, index) => { - return ( + let header; + let popoverContent; + let count; + + if (!_.isEmpty(docIds)) { + header = 'Documents'; + popoverContent = docIds.map((docId, index) => ( +
+ {index > 0 && } + +

{docId}

+
+
+ )); + count = popoverContent.length; + } else { + header = 'Queries'; + popoverContent = queries.map((query, index) => (
{index > 0 && } @@ -21,12 +38,15 @@ export default function QueryPopover(queries) {

{query.query}

- ); - }); + )); + count = popoverContent.length; + } + + const buttonContent = `${count} ${header}`; return ( {`${queries.queries.length} Queries`}} + button={{buttonContent}} isOpen={isPopoverOpen} closePopover={closePopover} > diff --git a/public/pages/Dashboard/components/FindingsDashboard/utils.js b/public/pages/Dashboard/components/FindingsDashboard/utils.js index 7ec7a6478..796b59f81 100644 --- a/public/pages/Dashboard/components/FindingsDashboard/utils.js +++ b/public/pages/Dashboard/components/FindingsDashboard/utils.js @@ -7,13 +7,30 @@ import React from 'react'; import _ from 'lodash'; import { renderTime } from '../../utils/tableUtils'; import FindingFlyout from './FindingFlyout'; -import QueryPopover from './QueriesPopover'; +import FindingsPopover from './FindingsPopover'; +import { QUERY_OPERATORS } from '../../../CreateMonitor/components/DocumentLevelMonitorQueries/DocumentLevelQuery'; export const TABLE_TAB_IDS = { ALERTS: { id: 'alerts', name: 'Alerts' }, FINDINGS: { id: 'findings', name: 'Document findings' }, }; +export const ALERTS_FINDING_COLUMN = { + field: 'related_doc_ids', + name: 'Document', + sortable: true, + truncateText: true, + render: (related_doc_ids, alert) => { + if (_.isEmpty(related_doc_ids)) + console.log('Alerts index contains an entry with 0 related document IDs:', alert); + return related_doc_ids.length > 1 ? ( + + ) : ( + related_doc_ids[0] + ); + }, +}; + export const findingsColumnTypes = (isAlertsFlyout) => [ { field: 'document_list', @@ -39,11 +56,11 @@ export const findingsColumnTypes = (isAlertsFlyout) => [ name: 'Query', sortable: true, truncateText: false, - render: (queries) => { + render: (queries, finding) => { if (_.isEmpty(queries)) - console.log('Findings index contains an entry with 0 queries:', queries); + console.log('Findings index contains an entry with 0 queries:', finding); return queries.length > 1 ? ( - + ) : ( `${queries[0].name} (${queries[0].query})` ); @@ -74,31 +91,36 @@ export const getFindingsForMonitor = (findings, monitorId) => { export const parseFindingsForPreview = (previewResponse, index, queries = []) => { // TODO FIXME: ExecuteMonitor API currently only returns a list of query names/IDs and the relevant docIds. // As a result, the preview dashboard cannot display document contents. - const timestamp = Date.now(); const findings = []; - const docIdsToQueries = {}; - - _.keys(previewResponse).forEach((queryName) => { - _.get(previewResponse, queryName, []).forEach((id) => { - if (_.includes(_.keys(docIdsToQueries), id)) { - const query = _.find(queries, { queryName: queryName }); - docIdsToQueries[id].push({ name: queryName, query: query.query }); - } else { - const query = _.find(queries, { queryName: queryName }); - docIdsToQueries[id] = [{ name: queryName, query: query.query }]; + if (validDocLevelGraphQueries(queries)) { + const queryNames = queries.map((query) => query.queryName); + const timestamp = Date.now(); + const docIdsToQueries = {}; + _.keys(previewResponse).forEach((queryName) => { + if (_.includes(queryNames, queryName)) { + _.get(previewResponse, queryName, []).forEach((id) => { + if (_.includes(_.keys(docIdsToQueries), id)) { + const query = _.find(queries, { queryName: queryName }); + docIdsToQueries[id].push({ name: queryName, query: query.query }); + } else { + const query = _.find(queries, { queryName: queryName }); + const operator = _.find(QUERY_OPERATORS, { value: query.operator }).text; + const querySource = `${query.field} ${operator} ${query.query}`; + docIdsToQueries[id] = [{ name: queryName, query: querySource }]; + } + }); } }); - }); - - _.keys(docIdsToQueries).forEach((docId) => { - const finding = { - index: index, - related_doc_id: docId, - queries: docIdsToQueries[docId], - timestamp: timestamp, - }; - findings.push(finding); - }); + _.keys(docIdsToQueries).forEach((docId) => { + const finding = { + index: index, + related_doc_id: docId, + queries: docIdsToQueries[docId], + timestamp: timestamp, + }; + findings.push(finding); + }); + } return findings; }; @@ -112,3 +134,12 @@ export const getPreviewResponseDocIds = (response) => { }); return docIds; }; + +export const validDocLevelGraphQueries = (queries) => { + // The 'queryName' and 'query' fields are required to execute a doc level query. + // If either are undefined for any queries, the monitor cannot be executed. + const allQueriesDefined = queries.find( + (query) => !_.isEmpty(query.queryName) && !_.isEmpty(query.query) + ); + return !_.isEmpty(allQueriesDefined); +}; diff --git a/public/pages/Dashboard/containers/FindingsDashboard.js b/public/pages/Dashboard/containers/FindingsDashboard.js index d62d8593b..481f929e1 100644 --- a/public/pages/Dashboard/containers/FindingsDashboard.js +++ b/public/pages/Dashboard/containers/FindingsDashboard.js @@ -6,7 +6,7 @@ import React, { Component } from 'react'; import _ from 'lodash'; import queryString from 'query-string'; -import { EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTable, EuiEmptyPrompt, EuiLoadingSpinner, EuiText } from '@elastic/eui'; import ContentPanel from '../../../components/ContentPanel'; import { backendErrorNotification } from '../../../utils/helpers'; import { DEFAULT_PAGE_SIZE_OPTIONS } from '../../Monitors/containers/Monitors/utils/constants'; @@ -29,6 +29,10 @@ export const GET_FINDINGS_PREVIEW_PARAMS = { sortField: GET_FINDINGS_SORT_FIELDS.TIMESTAMP, }; +export const NO_FINDINGS_TEXT = + 'There are no existing findings. Adjust document level queries to generate findings. Once a document is indexed that meets the query condition, the finding will show in this table.'; +export const MAX_FINDINGS_COUNT = 10000; + export default class FindingsDashboard extends Component { constructor(props) { super(props); @@ -54,18 +58,16 @@ export default class FindingsDashboard extends Component { componentDidMount() { const { isPreview = false } = this.props; - if (isPreview) { - this.getPreviewFindingsDocuments(); - } else { - const { id, from, size, search, sortField, sortDirection } = this.state; - this.getFindings(id, from, size, search, sortDirection, sortField); - } + if (isPreview) this.getPreviewFindingsDocuments(); + else this.getFindings(); } componentDidUpdate(prevProps, prevState) { const prevQuery = this.getQueryObjectFromState(prevState); const currQuery = this.getQueryObjectFromState(this.state); - if (!_.isEqual(prevQuery, currQuery)) this.componentDidMount(); + if (!_.isEqual(prevQuery, currQuery)) this.sortFindings(); + if (!_.isEqual(prevQuery.sortDirection, currQuery.sortDirection)) + this.setState({ findings: _.reverse(this.state.findings) }); } getURLQueryParams() { @@ -78,10 +80,15 @@ export default class FindingsDashboard extends Component { sortField = DEFAULT_GET_FINDINGS_PARAMS.sortField, sortDirection = DEFAULT_GET_FINDINGS_PARAMS.sortDirection, } = queryString.parse(location.search); + const parsedSize = isNaN(parseInt(size, 10)) + ? DEFAULT_GET_FINDINGS_PARAMS.size + : parseInt(size, 10); return { id, from: isNaN(parseInt(from, 10)) ? DEFAULT_GET_FINDINGS_PARAMS.from : parseInt(from, 10), - size: isNaN(parseInt(size, 10)) ? DEFAULT_GET_FINDINGS_PARAMS.size : parseInt(size, 10), + size: _.includes(DEFAULT_PAGE_SIZE_OPTIONS, parsedSize) + ? parsedSize + : DEFAULT_GET_FINDINGS_PARAMS.size, search, sortField: _.includes(_.values(GET_FINDINGS_SORT_FIELDS), sortField) ? sortField @@ -95,8 +102,9 @@ export default class FindingsDashboard extends Component { } getFindings = _.debounce( - (id, from, size, search, sortDirection, sortField) => { + () => { this.setState({ loadingFindings: true }); + const { id, from, size, search, sortField, sortDirection } = this.state; const params = { id, from, @@ -106,6 +114,11 @@ export default class FindingsDashboard extends Component { sortField, }; const queryParamsString = queryString.stringify(params); + + // TODO FIXME: Refactor 'size' logic to return all findings for a monitor + // once the backend supports retrieving findings for a monitorId. + params['size'] = Math.max(size, MAX_FINDINGS_COUNT); + location.search; const { httpClient, history, monitorId, notifications } = this.props; history.replace({ ...this.props.location, search: queryParamsString }); @@ -113,6 +126,7 @@ export default class FindingsDashboard extends Component { httpClient.get('../api/alerting/findings/_search', { query: params }).then((resp) => { if (resp.ok) { this.setState({ ...getFindingsForMonitor(resp.findings, monitorId) }); + this.sortFindings(); } else { console.log('Error getting findings:', resp); backendErrorNotification(notifications, 'get', 'findings', resp.err); @@ -124,6 +138,35 @@ export default class FindingsDashboard extends Component { { leading: true } ); + sortFindings() { + this.setState({ loadingFindings: true }); + const { findings, sortField } = this.state; + let sortedFindings; + switch (sortField) { + case 'document_list': + sortedFindings = _.orderBy(findings, (finding) => _.get(finding, 'document_list.0.id', '')); + break; + case GET_FINDINGS_SORT_FIELDS.INDEX: + sortedFindings = _.orderBy(findings, (finding) => + _.get(finding, GET_FINDINGS_SORT_FIELDS.INDEX, '') + ); + break; + case GET_FINDINGS_SORT_FIELDS.MONITOR_NAME: + sortedFindings = _.orderBy(findings, (finding) => + _.get(finding, GET_FINDINGS_SORT_FIELDS.MONITOR_NAME, '') + ); + break; + case 'queries': + sortedFindings = _.orderBy(findings, (finding) => _.get(finding, 'queries', []).length); + break; + default: + sortedFindings = _.orderBy(findings, (finding) => + _.get(finding, GET_FINDINGS_SORT_FIELDS.TIMESTAMP, '') + ); + } + this.setState({ findings: sortedFindings, loadingFindings: false }); + } + getPreviewFindingsDocuments() { this.setState({ loadingFindings: true }); const { index, queries, previewResponse } = this.props; @@ -154,7 +197,7 @@ export default class FindingsDashboard extends Component { const pagination = { pageIndex: page, pageSize: size, - totalItemCount: Math.min(size, totalFindings), + totalItemCount: totalFindings, pageSizeOptions: DEFAULT_PAGE_SIZE_OPTIONS, }; @@ -167,6 +210,7 @@ export default class FindingsDashboard extends Component { const getItemId = (item) => item.id; + const paginatedFindings = findings.slice(page * size, page * size + size); return ( + ) : ( + +

{NO_FINDINGS_TEXT}

+ + } + /> + ) + } />
); diff --git a/server/services/AlertService.js b/server/services/AlertService.js index cf2f1ba9a..80d0a1d8b 100644 --- a/server/services/AlertService.js +++ b/server/services/AlertService.js @@ -24,10 +24,7 @@ export default class AlertService { size = 20, search = '', sortDirection = 'desc', - // If the sortField parsed from the URL isn't a valid option for this API, use a default option. - sortField = _.includes(_.values(GET_ALERTS_SORT_FILTERS), req.query.sortField) - ? req.query.sortField - : GET_ALERTS_SORT_FILTERS.START_TIME, + sortField = GET_ALERTS_SORT_FILTERS.START_TIME, severityLevel = 'ALL', alertState = 'ALL', monitorIds = [], @@ -47,12 +44,6 @@ export default class AlertService { sortOrder: sortDirection, }; break; - case GET_ALERTS_SORT_FILTERS.START_TIME: - params = { - sortString: sortField, - sortOrder: sortDirection, - }; - break; case GET_ALERTS_SORT_FILTERS.END_TIME: params = { sortString: sortField, @@ -67,6 +58,12 @@ export default class AlertService { missing: '_last', }; break; + default: + // If the sortField parsed from the URL isn't a valid option for this API, use a default option. + params = { + sortString: GET_ALERTS_SORT_FILTERS.START_TIME, + sortOrder: sortDirection, + }; } params.startIndex = from; diff --git a/server/services/FindingService.js b/server/services/FindingService.js index 177c3be29..c9c1e568f 100644 --- a/server/services/FindingService.js +++ b/server/services/FindingService.js @@ -36,10 +36,7 @@ export default class FindingService { size = DEFAULT_GET_FINDINGS_PARAMS.size, search = DEFAULT_GET_FINDINGS_PARAMS.search, sortDirection = DEFAULT_GET_FINDINGS_PARAMS.sortDirection, - // If the sortField parsed from the URL isn't a valid option for this API, use a default option. - sortField = _.includes(_.values(GET_FINDINGS_SORT_FIELDS), req.query.sortField) - ? req.query.sortField - : DEFAULT_GET_FINDINGS_PARAMS.sortField, + sortField = DEFAULT_GET_FINDINGS_PARAMS.sortField, } = req.query; var params; @@ -56,12 +53,12 @@ export default class FindingService { sortOrder: sortDirection, }; break; - case GET_FINDINGS_SORT_FIELDS.TIMESTAMP: + default: + // If the sortField parsed from the URL isn't a valid option for this API, use a default option. params = { - sortString: sortField, + sortString: GET_FINDINGS_SORT_FIELDS.TIMESTAMP, sortOrder: sortDirection, }; - break; } if (!_.isEmpty(id)) params.findingId = id;