[Synthetics UI] Add pagination and date filtering to test runs table (#144029)

Co-authored-by: Shahzad <shahzad.muhammad@elastic.co>
This commit is contained in:
Alejandro Fernández Gómez 2022-10-31 13:33:32 +01:00 committed by GitHub
parent 7f8e78f844
commit fdce0662f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 321 additions and 187 deletions

View file

@ -289,6 +289,7 @@ export const GetPingsParamsType = t.intersection([
excludedLocations: t.string,
index: t.number,
size: t.number,
pageIndex: t.number,
locations: t.string,
monitorId: t.string,
sort: t.string,

View file

@ -0,0 +1,61 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelectedMonitor } from './use_selected_monitor';
import { useSelectedLocation } from './use_selected_location';
import { getMonitorRecentPingsAction, selectMonitorPingsMetadata } from '../../../state';
interface UseMonitorPingsProps {
pageSize?: number;
pageIndex?: number;
from?: string;
to?: string;
}
export const useMonitorPings = (props?: UseMonitorPingsProps) => {
const dispatch = useDispatch();
const { monitor } = useSelectedMonitor();
const location = useSelectedLocation();
const monitorId = monitor?.id;
const locationLabel = location?.label;
useEffect(() => {
if (monitorId && locationLabel) {
dispatch(
getMonitorRecentPingsAction.get({
monitorId,
locationId: locationLabel,
size: props?.pageSize,
pageIndex: props?.pageIndex,
from: props?.from,
to: props?.to,
})
);
}
}, [
dispatch,
monitorId,
locationLabel,
props?.pageSize,
props?.pageIndex,
props?.from,
props?.to,
]);
const { total, data: pings, loading } = useSelector(selectMonitorPingsMetadata);
return {
loading,
total,
pings,
};
};

View file

@ -26,7 +26,7 @@ import { DurationPanel } from './duration_panel';
import { MonitorDetailsPanel } from './monitor_details_panel';
import { AvailabilitySparklines } from './availability_sparklines';
import { LastTestRun } from './last_test_run';
import { LastTenTestRuns } from './last_ten_test_runs';
import { TestRunsTable } from './test_runs_table';
import { MonitorErrorsCount } from './monitor_errors_count';
export const MonitorSummary = () => {
@ -107,7 +107,7 @@ export const MonitorSummary = () => {
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<LastTenTestRuns />
<TestRunsTable paginable={false} from={from} to={to} />
</>
);
};

View file

@ -31,27 +31,44 @@ import {
import { useSyntheticsSettingsContext } from '../../../contexts/synthetics_settings_context';
import { sortPings } from '../../../utils/monitor_test_result/sort_pings';
import { selectPingsLoading, selectMonitorRecentPings, selectPingsError } from '../../../state';
import { selectPingsError } from '../../../state';
import { parseBadgeStatus, StatusBadge } from '../../common/monitor_test_result/status_badge';
import { isStepEnd } from '../../common/monitor_test_result/browser_steps_list';
import { JourneyStepScreenshotContainer } from '../../common/monitor_test_result/journey_step_screenshot_container';
import { useKibanaDateFormat } from '../../../../../hooks/use_kibana_date_format';
import { useSelectedMonitor } from '../hooks/use_selected_monitor';
import { useMonitorPings } from '../hooks/use_monitor_pings';
import { useJourneySteps } from '../hooks/use_journey_steps';
type SortableField = 'timestamp' | 'monitor.status' | 'monitor.duration.us';
export const LastTenTestRuns = () => {
interface TestRunsTableProps {
from: string;
to: string;
paginable?: boolean;
}
export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps) => {
const { basePath } = useSyntheticsSettingsContext();
const [page, setPage] = useState({ index: 0, size: 10 });
const [sortField, setSortField] = useState<SortableField>('timestamp');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const pings = useSelector(selectMonitorRecentPings);
const {
pings,
total,
loading: pingsLoading,
} = useMonitorPings({
from,
to,
pageSize: page.size,
pageIndex: page.index,
});
const sortedPings = useMemo(() => {
return sortPings(pings, sortField, sortDirection);
}, [pings, sortField, sortDirection]);
const pingsLoading = useSelector(selectPingsLoading);
const pingsError = useSelector(selectPingsError);
const { monitor } = useSelectedMonitor();
@ -64,7 +81,10 @@ export const LastTenTestRuns = () => {
},
};
const handleTableChange = ({ page, sort }: Criteria<Ping>) => {
const handleTableChange = ({ page: newPage, sort }: Criteria<Ping>) => {
if (newPage !== undefined) {
setPage(newPage);
}
if (sort !== undefined) {
setSortField(sort.field as SortableField);
setSortDirection(sort.direction);
@ -125,7 +145,7 @@ export const LastTenTestRuns = () => {
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h3>{pings?.length >= 10 ? LAST_10_TEST_RUNS : TEST_RUNS}</h3>
<h3>{paginable || pings?.length < 10 ? TEST_RUNS : LAST_10_TEST_RUNS}</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={true} />
@ -162,6 +182,16 @@ export const LastTenTestRuns = () => {
tableLayout={'auto'}
sorting={sorting}
onChange={handleTableChange}
pagination={
paginable
? {
pageIndex: page.index,
pageSize: page.size,
totalItemCount: total,
pageSizeOptions: [10, 20, 50], // TODO Confirm with Henry,
}
: undefined
}
/>
</EuiPanel>
);

View file

@ -26,6 +26,13 @@ export const getMonitorAction = createAsyncAction<
>('[MONITOR DETAILS] GET MONITOR');
export const getMonitorRecentPingsAction = createAsyncAction<
{ monitorId: string; locationId: string },
{
monitorId: string;
locationId: string;
size?: number;
pageIndex?: number;
from?: string;
to?: string;
},
PingsResponse
>('[MONITOR DETAILS] GET RECENT PINGS');

View file

@ -24,19 +24,32 @@ export interface QueryParams {
export const fetchMonitorRecentPings = async ({
monitorId,
locationId,
from,
to,
size = 10,
pageIndex = 0,
}: {
monitorId: string;
locationId: string;
from?: string;
to?: string;
size?: number;
pageIndex?: number;
}): Promise<PingsResponse> => {
const from = new Date(0).toISOString();
const to = new Date().toISOString();
const locations = JSON.stringify([locationId]);
const sort = 'desc';
const size = 10;
return await apiService.get(
SYNTHETICS_API_URLS.PINGS,
{ monitorId, from, to, locations, sort, size },
{
monitorId,
from: from ?? new Date(0).toISOString(),
to: to ?? new Date().toISOString(),
locations,
sort,
size,
pageIndex,
},
PingsResponseType
);
};

View file

@ -18,8 +18,11 @@ import {
} from './actions';
export interface MonitorDetailsState {
pings: Ping[];
loading: boolean;
pings: {
total: number;
data: Ping[];
loading: boolean;
};
syntheticsMonitorLoading: boolean;
syntheticsMonitor: EncryptedSyntheticsSavedMonitor | null;
error: IHttpSerializedFetchError | null;
@ -27,8 +30,7 @@ export interface MonitorDetailsState {
}
const initialState: MonitorDetailsState = {
pings: [],
loading: false,
pings: { total: 0, data: [], loading: false },
syntheticsMonitor: null,
syntheticsMonitorLoading: false,
error: null,
@ -42,16 +44,19 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => {
})
.addCase(getMonitorRecentPingsAction.get, (state, action) => {
state.loading = true;
state.pings = state.pings.filter((ping) => !checkIsStalePing(action.payload.monitorId, ping));
state.pings.loading = true;
state.pings.data = state.pings.data.filter(
(ping) => !checkIsStalePing(action.payload.monitorId, ping)
);
})
.addCase(getMonitorRecentPingsAction.success, (state, action) => {
state.pings = action.payload.pings;
state.loading = false;
state.pings.total = action.payload.total;
state.pings.data = action.payload.pings;
state.pings.loading = false;
})
.addCase(getMonitorRecentPingsAction.fail, (state, action) => {
state.error = action.payload;
state.loading = false;
state.pings.loading = false;
})
.addCase(getMonitorAction.get, (state) => {

View file

@ -17,10 +17,10 @@ export const selectSelectedLocationId = createSelector(
(state) => state.selectedLocationId
);
export const selectLatestPing = createSelector(getState, (state) => state.pings?.[0] ?? null);
export const selectLatestPing = createSelector(getState, (state) => state.pings.data[0] ?? null);
export const selectPingsLoading = createSelector(getState, (state) => state.loading);
export const selectPingsLoading = createSelector(getState, (state) => state.pings.loading);
export const selectMonitorRecentPings = createSelector(getState, (state) => state.pings);
export const selectMonitorPingsMetadata = createSelector(getState, (state) => state.pings);
export const selectPingsError = createSelector(getState, (state) => state.error);

View file

@ -132,171 +132,174 @@ function getBrowserJourneyMockSlice() {
function getMonitorDetailsMockSlice() {
return {
pings: [
{
summary: { up: 1, down: 0 },
agent: {
name: 'cron-b010e1cc9518984e-27644714-4pd4h',
id: 'f8721d90-5aec-4815-a6f1-f4d4a6fb7482',
type: 'heartbeat',
ephemeral_id: 'd6a60494-5e52-418f-922b-8e90f0b4013c',
version: '8.3.0',
},
synthetics: {
journey: { name: 'inline', id: 'inline', tags: null },
type: 'heartbeat/summary',
},
monitor: {
duration: { us: 269722 },
origin: SourceType.UI,
name: 'One pixel monitor',
check_group: '051aba1c-0b74-11ed-9f0e-ba4e6fa109d5',
id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
timespan: { lt: '2022-07-24T17:24:06.094Z', gte: '2022-07-24T17:14:06.094Z' },
type: DataStream.BROWSER,
status: 'up',
},
url: {
scheme: 'data',
domain: '',
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
geo: {
continent_name: 'North America',
city_name: 'Iowa',
country_iso_code: 'US',
name: 'North America - US Central',
location: '41.8780, 93.0977',
pings: {
total: 3,
data: [
{
summary: { up: 1, down: 0 },
agent: {
name: 'cron-b010e1cc9518984e-27644714-4pd4h',
id: 'f8721d90-5aec-4815-a6f1-f4d4a6fb7482',
type: 'heartbeat',
ephemeral_id: 'd6a60494-5e52-418f-922b-8e90f0b4013c',
version: '8.3.0',
},
hostname: 'cron-b010e1cc9518984e-27644714-4pd4h',
ip: ['10.1.11.162'],
mac: ['ba:4e:6f:a1:09:d5'],
},
'@timestamp': '2022-07-24T17:14:05.079Z',
ecs: { version: '8.0.0' },
config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' },
'event.type': 'journey/end',
event: {
agent_id_status: 'auth_metadata_missing',
ingested: '2022-07-24T17:14:07Z',
type: 'heartbeat/summary',
dataset: 'browser',
},
timestamp: '2022-07-24T17:14:05.079Z',
docId: 'AkYzMYIBqL6WCtugsFck',
},
{
summary: { up: 1, down: 0 },
agent: {
name: 'cron-b010e1cc9518984e-27644704-zs98t',
id: 'a9620214-591d-48e7-9e5d-10b7a9fb1a03',
type: 'heartbeat',
ephemeral_id: 'c5110885-81b4-4e9a-8747-690d19fbd225',
version: '8.3.0',
},
synthetics: {
journey: { name: 'inline', id: 'inline', tags: null },
type: 'heartbeat/summary',
},
monitor: {
duration: { us: 227326 },
origin: SourceType.UI,
name: 'One pixel monitor',
id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
check_group: '9eb87e53-0b72-11ed-b34f-aa618b4334ae',
timespan: { lt: '2022-07-24T17:14:05.020Z', gte: '2022-07-24T17:04:05.020Z' },
type: DataStream.BROWSER,
status: 'up',
},
url: {
scheme: 'data',
domain: '',
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
geo: {
continent_name: 'North America',
city_name: 'Iowa',
country_iso_code: 'US',
name: 'North America - US Central',
location: '41.8780, 93.0977',
synthetics: {
journey: { name: 'inline', id: 'inline', tags: null },
type: 'heartbeat/summary',
},
hostname: 'cron-b010e1cc9518984e-27644704-zs98t',
ip: ['10.1.9.133'],
mac: ['aa:61:8b:43:34:ae'],
},
'@timestamp': '2022-07-24T17:04:03.769Z',
ecs: { version: '8.0.0' },
config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' },
'event.type': 'journey/end',
event: {
agent_id_status: 'auth_metadata_missing',
ingested: '2022-07-24T17:04:06Z',
type: 'heartbeat/summary',
dataset: 'browser',
},
timestamp: '2022-07-24T17:04:03.769Z',
docId: 'mkYqMYIBqL6WCtughFUq',
},
{
summary: { up: 1, down: 0 },
agent: {
name: 'job-b010e1cc9518984e-dkw5k',
id: 'e3a4e3a8-bdd1-44fe-86f5-e451b80f80c5',
type: 'heartbeat',
ephemeral_id: 'f41a13ab-a85d-4614-89c0-8dbad6a32868',
version: '8.3.0',
},
synthetics: {
journey: { name: 'inline', id: 'inline', tags: null },
type: 'heartbeat/summary',
},
monitor: {
duration: { us: 207700 },
origin: SourceType.UI,
name: 'One pixel monitor',
timespan: { lt: '2022-07-24T17:11:49.702Z', gte: '2022-07-24T17:01:49.702Z' },
check_group: '4e00ac5a-0b72-11ed-a97e-5203642c687d',
id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
type: DataStream.BROWSER,
status: 'up',
},
url: {
scheme: 'data',
domain: '',
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
geo: {
continent_name: 'North America',
city_name: 'Iowa',
country_iso_code: 'US',
name: 'North America - US Central',
location: '41.8780, 93.0977',
monitor: {
duration: { us: 269722 },
origin: SourceType.UI,
name: 'One pixel monitor',
check_group: '051aba1c-0b74-11ed-9f0e-ba4e6fa109d5',
id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
timespan: { lt: '2022-07-24T17:24:06.094Z', gte: '2022-07-24T17:14:06.094Z' },
type: DataStream.BROWSER,
status: 'up',
},
hostname: 'job-b010e1cc9518984e-dkw5k',
ip: ['10.1.9.132'],
mac: ['52:03:64:2c:68:7d'],
url: {
scheme: 'data',
domain: '',
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
geo: {
continent_name: 'North America',
city_name: 'Iowa',
country_iso_code: 'US',
name: 'North America - US Central',
location: '41.8780, 93.0977',
},
hostname: 'cron-b010e1cc9518984e-27644714-4pd4h',
ip: ['10.1.11.162'],
mac: ['ba:4e:6f:a1:09:d5'],
},
'@timestamp': '2022-07-24T17:14:05.079Z',
ecs: { version: '8.0.0' },
config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' },
'event.type': 'journey/end',
event: {
agent_id_status: 'auth_metadata_missing',
ingested: '2022-07-24T17:14:07Z',
type: 'heartbeat/summary',
dataset: 'browser',
},
timestamp: '2022-07-24T17:14:05.079Z',
docId: 'AkYzMYIBqL6WCtugsFck',
},
'@timestamp': '2022-07-24T17:01:48.326Z',
ecs: { version: '8.0.0' },
config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' },
'event.type': 'journey/end',
event: {
agent_id_status: 'auth_metadata_missing',
ingested: '2022-07-24T17:01:50Z',
type: 'heartbeat/summary',
dataset: 'browser',
{
summary: { up: 1, down: 0 },
agent: {
name: 'cron-b010e1cc9518984e-27644704-zs98t',
id: 'a9620214-591d-48e7-9e5d-10b7a9fb1a03',
type: 'heartbeat',
ephemeral_id: 'c5110885-81b4-4e9a-8747-690d19fbd225',
version: '8.3.0',
},
synthetics: {
journey: { name: 'inline', id: 'inline', tags: null },
type: 'heartbeat/summary',
},
monitor: {
duration: { us: 227326 },
origin: SourceType.UI,
name: 'One pixel monitor',
id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
check_group: '9eb87e53-0b72-11ed-b34f-aa618b4334ae',
timespan: { lt: '2022-07-24T17:14:05.020Z', gte: '2022-07-24T17:04:05.020Z' },
type: DataStream.BROWSER,
status: 'up',
},
url: {
scheme: 'data',
domain: '',
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
geo: {
continent_name: 'North America',
city_name: 'Iowa',
country_iso_code: 'US',
name: 'North America - US Central',
location: '41.8780, 93.0977',
},
hostname: 'cron-b010e1cc9518984e-27644704-zs98t',
ip: ['10.1.9.133'],
mac: ['aa:61:8b:43:34:ae'],
},
'@timestamp': '2022-07-24T17:04:03.769Z',
ecs: { version: '8.0.0' },
config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' },
'event.type': 'journey/end',
event: {
agent_id_status: 'auth_metadata_missing',
ingested: '2022-07-24T17:04:06Z',
type: 'heartbeat/summary',
dataset: 'browser',
},
timestamp: '2022-07-24T17:04:03.769Z',
docId: 'mkYqMYIBqL6WCtughFUq',
},
timestamp: '2022-07-24T17:01:48.326Z',
docId: 'kUYoMYIBqL6WCtugc1We',
},
],
loading: false,
{
summary: { up: 1, down: 0 },
agent: {
name: 'job-b010e1cc9518984e-dkw5k',
id: 'e3a4e3a8-bdd1-44fe-86f5-e451b80f80c5',
type: 'heartbeat',
ephemeral_id: 'f41a13ab-a85d-4614-89c0-8dbad6a32868',
version: '8.3.0',
},
synthetics: {
journey: { name: 'inline', id: 'inline', tags: null },
type: 'heartbeat/summary',
},
monitor: {
duration: { us: 207700 },
origin: SourceType.UI,
name: 'One pixel monitor',
timespan: { lt: '2022-07-24T17:11:49.702Z', gte: '2022-07-24T17:01:49.702Z' },
check_group: '4e00ac5a-0b72-11ed-a97e-5203642c687d',
id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
type: DataStream.BROWSER,
status: 'up',
},
url: {
scheme: 'data',
domain: '',
full: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
},
observer: {
geo: {
continent_name: 'North America',
city_name: 'Iowa',
country_iso_code: 'US',
name: 'North America - US Central',
location: '41.8780, 93.0977',
},
hostname: 'job-b010e1cc9518984e-dkw5k',
ip: ['10.1.9.132'],
mac: ['52:03:64:2c:68:7d'],
},
'@timestamp': '2022-07-24T17:01:48.326Z',
ecs: { version: '8.0.0' },
config_id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
data_stream: { namespace: 'default', type: 'synthetics', dataset: 'browser' },
'event.type': 'journey/end',
event: {
agent_id_status: 'auth_metadata_missing',
ingested: '2022-07-24T17:01:50Z',
type: 'heartbeat/summary',
dataset: 'browser',
},
timestamp: '2022-07-24T17:01:48.326Z',
docId: 'kUYoMYIBqL6WCtugc1We',
},
],
loading: false,
},
syntheticsMonitor: {
id: '4afd3980-0b72-11ed-9c10-b57918ea89d6',
type: DataStream.BROWSER,

View file

@ -68,6 +68,7 @@ export const queryPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> =
status,
sort,
size: sizeParam,
pageIndex,
locations,
excludedLocations,
}) => {
@ -75,6 +76,7 @@ export const queryPings: UMElasticsearchQueryFn<GetPingsParams, PingsResponse> =
const searchBody = {
size,
from: pageIndex !== undefined ? pageIndex * size : 0,
...(index ? { from: index * size } : {}),
query: {
bool: {

View file

@ -23,13 +23,24 @@ export const syntheticsGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLib
monitorId: schema.maybe(schema.string()),
index: schema.maybe(schema.number()),
size: schema.maybe(schema.number()),
pageIndex: schema.maybe(schema.number()),
sort: schema.maybe(schema.string()),
status: schema.maybe(schema.string()),
}),
},
handler: async ({ uptimeEsClient, request, response }): Promise<any> => {
const { from, to, index, monitorId, status, sort, size, locations, excludedLocations } =
request.query;
const {
from,
to,
index,
monitorId,
status,
sort,
size,
pageIndex,
locations,
excludedLocations,
} = request.query;
return await queryPings({
uptimeEsClient,
@ -39,6 +50,7 @@ export const syntheticsGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLib
status,
sort,
size,
pageIndex,
locations: locations ? JSON.parse(locations) : [],
excludedLocations,
});