Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 14 commits into from
Oct 31, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -107,7 +107,7 @@ export const MonitorSummary = () => {
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<LastTenTestRuns />
<TestRunsTable paginable={false} from={from} to={to} />
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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);
Expand Down Expand Up @@ -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} />
Expand Down Expand Up @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ 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;
selectedLocationId: string | null;
}

const initialState: MonitorDetailsState = {
pings: [],
loading: false,
pings: { total: 0, data: [], loading: false },
syntheticsMonitor: null,
syntheticsMonitorLoading: false,
error: null,
Expand All @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Loading