From 0e967a3a2250e52e6184a4649465d5d5e5c7d114 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 20 Feb 2020 04:08:01 +1300 Subject: [PATCH] adds pagination on Alert Instances list on Alert Details page (#57524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added pagination on alert instances page * extracted default page size to a constant for alerting UI as a whole * Fix test failure Co-authored-by: Elastic Machine Co-authored-by: Mike Côté --- .../public/application/constants/index.ts | 2 + .../components/alert_instances.tsx | 44 ++++++--- .../alerts_list/components/alerts_list.tsx | 4 +- .../apps/triggers_actions_ui/details.ts | 93 ++++++++++++++++++- .../page_objects/alert_details.ts | 4 + 5 files changed, 133 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 11b094dea0e624..d469651b48b04a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -20,3 +20,5 @@ export enum SORT_ORDERS { ASCENDING = 'asc', DESCENDING = 'desc', } + +export const DEFAULT_SEARCH_PAGE_SIZE: number = 10; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx index 731489c61d60f1..98aa981f40d116 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances.tsx @@ -4,19 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; +import React, { Fragment, useState } from 'react'; import moment, { Duration } from 'moment'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiButtonToggle, EuiBadge, EuiHealth } from '@elastic/eui'; // @ts-ignore import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '@elastic/eui/lib/services'; -import { padLeft, difference } from 'lodash'; +import { padLeft, difference, chunk } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Alert, AlertTaskState, RawAlertInstance } from '../../../../types'; +import { Alert, AlertTaskState, RawAlertInstance, Pagination } from '../../../../types'; import { ComponentOpts as AlertApis, withBulkAlertOperations, } from '../../common/components/with_bulk_alert_api_operations'; +import { DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; type AlertInstancesProps = { alert: Alert; @@ -134,22 +135,39 @@ export function AlertInstances({ unmuteAlertInstance, requestRefresh, }: AlertInstancesProps) { + const [pagination, setPagination] = useState({ + index: 0, + size: DEFAULT_SEARCH_PAGE_SIZE, + }); + + const mergedAlertInstances = [ + ...Object.entries(alertInstances).map(([instanceId, instance]) => + alertInstanceToListItem(alert, instanceId, instance) + ), + ...difference(alert.mutedInstanceIds, Object.keys(alertInstances)).map(instanceId => + alertInstanceToListItem(alert, instanceId) + ), + ]; + const pageOfAlertInstances = getPage(mergedAlertInstances, pagination); + const onMuteAction = async (instance: AlertInstanceListItem) => { await (instance.isMuted ? unmuteAlertInstance(alert, instance.instance) : muteAlertInstance(alert, instance.instance)); requestRefresh(); }; + return ( - alertInstanceToListItem(alert, instanceId, instance) - ), - ...difference(alert.mutedInstanceIds, Object.keys(alertInstances)).map(instanceId => - alertInstanceToListItem(alert, instanceId) - ), - ]} + items={pageOfAlertInstances} + pagination={{ + pageIndex: pagination.index, + pageSize: pagination.size, + totalItemCount: mergedAlertInstances.length, + }} + onChange={({ page: changedPage }: { page: Pagination }) => { + setPagination(changedPage); + }} rowProps={() => ({ 'data-test-subj': 'alert-instance-row', })} @@ -163,6 +181,10 @@ export function AlertInstances({ } export const AlertInstancesWithApi = withBulkAlertOperations(AlertInstances); +function getPage(items: any[], pagination: Pagination) { + return chunk(items, pagination.size)[pagination.index] || []; +} + interface AlertInstanceListItemStatus { label: string; healthColor: string; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index a89215e6c29648..d9ccb84452e47e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -32,7 +32,7 @@ import { ActionTypeFilter } from './action_type_filter'; import { loadAlerts, loadAlertTypes } from '../../../lib/alert_api'; import { loadActionTypes } from '../../../lib/action_connector_api'; import { hasDeleteAlertsCapability, hasSaveAlertsCapability } from '../../../lib/capabilities'; -import { routeToAlertDetails } from '../../../constants'; +import { routeToAlertDetails, DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; const ENTER_KEY = 13; @@ -67,7 +67,7 @@ export const AlertsList: React.FunctionComponent = () => { const [actionTypes, setActionTypes] = useState([]); const [selectedIds, setSelectedIds] = useState([]); const [isPerformingAction, setIsPerformingAction] = useState(false); - const [page, setPage] = useState({ index: 0, size: 10 }); + const [page, setPage] = useState({ index: 0, size: DEFAULT_SEARCH_PAGE_SIZE }); const [searchText, setSearchText] = useState(); const [inputText, setInputText] = useState(); const [typesFilter, setTypesFilter] = useState([]); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 95371b5b501f5e..3db4731f0adfb6 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import uuid from 'uuid'; -import { omit, mapValues } from 'lodash'; +import { omit, mapValues, range, flatten } from 'lodash'; import moment from 'moment'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -331,5 +331,96 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.alertDetailsUI.ensureAlertInstanceExistance('eu-east', false); }); }); + + describe('Alert Instance Pagination', function() { + const testRunUuid = uuid.v4(); + let alert: any; + + before(async () => { + await pageObjects.common.navigateToApp('triggersActions'); + + const actions = await Promise.all([ + alerting.actions.createAction({ + name: `server-log-${testRunUuid}-${0}`, + actionTypeId: '.server-log', + config: {}, + secrets: {}, + }), + alerting.actions.createAction({ + name: `server-log-${testRunUuid}-${1}`, + actionTypeId: '.server-log', + config: {}, + secrets: {}, + }), + ]); + + const instances = flatten( + range(10).map(index => [ + { id: `us-central-${index}` }, + { id: `us-east-${index}` }, + { id: `us-west-${index}` }, + ]) + ); + alert = await alerting.alerts.createAlwaysFiringWithActions( + `test-alert-${testRunUuid}`, + actions.map(action => ({ + id: action.id, + group: 'default', + params: { + message: 'from alert 1s', + level: 'warn', + }, + })), + { + instances, + } + ); + + // refresh to see alert + await browser.refresh(); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Verify content + await testSubjects.existOrFail('alertsList'); + + // click on first alert + await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name); + + // await first run to complete so we have an initial state + await retry.try(async () => { + const { alertInstances } = await alerting.alerts.getAlertState(alert.id); + expect(Object.keys(alertInstances).length).to.eql(instances.length); + }); + }); + + const PAGE_SIZE = 10; + it('renders the first page', async () => { + // Verify content + await testSubjects.existOrFail('alertInstancesList'); + + const { alertInstances } = await alerting.alerts.getAlertState(alert.id); + + const items = await pageObjects.alertDetailsUI.getAlertInstancesList(); + expect(items.length).to.eql(PAGE_SIZE); + + const [firstItem] = items; + expect(firstItem.instance).to.eql(Object.keys(alertInstances)[0]); + }); + + it('navigates to the next page', async () => { + // Verify content + await testSubjects.existOrFail('alertInstancesList'); + + const { alertInstances } = await alerting.alerts.getAlertState(alert.id); + + await pageObjects.alertDetailsUI.clickPaginationNextPage(); + + await retry.try(async () => { + const [firstItem] = await pageObjects.alertDetailsUI.getAlertInstancesList(); + expect(firstItem.instance).to.eql(Object.keys(alertInstances)[PAGE_SIZE]); + }); + }); + }); }); }; diff --git a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts index fd936b37386770..900fe3237ffac0 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts @@ -92,5 +92,9 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { ).to.eql(shouldExist ? 1 : 0); }); }, + async clickPaginationNextPage() { + const nextButton = await testSubjects.find(`pagination-button-next`); + nextButton.click(); + }, }; }