mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Uptime] Added test now mode for monitors (#123712)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
52f0ea20fe
commit
7369e2f48d
35 changed files with 895 additions and 81 deletions
|
@ -41,4 +41,5 @@ export enum API_URLS {
|
|||
SERVICE_LOCATIONS = '/internal/uptime/service/locations',
|
||||
SYNTHETICS_MONITORS = '/internal/uptime/service/monitors',
|
||||
RUN_ONCE_MONITOR = '/internal/uptime/service/monitors/run_once',
|
||||
TRIGGER_MONITOR = '/internal/uptime/service/monitors/trigger',
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ export const StateType = t.intersection([
|
|||
monitor: t.intersection([
|
||||
t.partial({
|
||||
name: t.string,
|
||||
duration: t.type({ us: t.number }),
|
||||
}),
|
||||
t.type({
|
||||
type: t.string,
|
||||
|
@ -73,6 +74,7 @@ export const MonitorSummaryType = t.intersection([
|
|||
t.partial({
|
||||
histogram: HistogramType,
|
||||
minInterval: t.number,
|
||||
configId: t.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -220,6 +220,7 @@ export const PingType = t.intersection([
|
|||
service: t.partial({
|
||||
name: t.string,
|
||||
}),
|
||||
config_id: t.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ interface Props {
|
|||
export const BrowserTestRunResult = ({ monitorId }: Props) => {
|
||||
const { data, loading, stepEnds, journeyStarted, summaryDoc, stepListData } =
|
||||
useBrowserRunOnceMonitors({
|
||||
monitorId,
|
||||
configId: monitorId,
|
||||
});
|
||||
|
||||
const hits = data?.hits.hits;
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('useBrowserRunOnceMonitors', function () {
|
|||
},
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useBrowserRunOnceMonitors({ monitorId: 'test-id' }), {
|
||||
const { result } = renderHook(() => useBrowserRunOnceMonitors({ configId: 'test-id' }), {
|
||||
wrapper: WrappedHelper,
|
||||
});
|
||||
|
||||
|
|
|
@ -15,10 +15,12 @@ import { fetchJourneySteps } from '../../../../state/api/journey';
|
|||
import { isStepEnd } from '../../../synthetics/check_steps/steps_list';
|
||||
|
||||
export const useBrowserEsResults = ({
|
||||
monitorId,
|
||||
configId,
|
||||
testRunId,
|
||||
lastRefresh,
|
||||
}: {
|
||||
monitorId: string;
|
||||
configId: string;
|
||||
testRunId?: string;
|
||||
lastRefresh: number;
|
||||
}) => {
|
||||
const { settings } = useSelector(selectDynamicSettings);
|
||||
|
@ -37,7 +39,7 @@ export const useBrowserEsResults = ({
|
|||
filter: [
|
||||
{
|
||||
term: {
|
||||
config_id: monitorId,
|
||||
config_id: configId,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -45,28 +47,47 @@ export const useBrowserEsResults = ({
|
|||
'synthetics.type': ['heartbeat/summary', 'journey/start'],
|
||||
},
|
||||
},
|
||||
...(testRunId
|
||||
? [
|
||||
{
|
||||
term: {
|
||||
test_run_id: testRunId,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
size: 10,
|
||||
}),
|
||||
[monitorId, settings?.heartbeatIndices, lastRefresh],
|
||||
[configId, settings?.heartbeatIndices, lastRefresh],
|
||||
{ name: 'TestRunData' }
|
||||
);
|
||||
};
|
||||
|
||||
export const useBrowserRunOnceMonitors = ({ monitorId }: { monitorId: string }) => {
|
||||
const { refreshTimer, lastRefresh } = useTickTick();
|
||||
export const useBrowserRunOnceMonitors = ({
|
||||
configId,
|
||||
testRunId,
|
||||
skipDetails = false,
|
||||
refresh = true,
|
||||
}: {
|
||||
configId: string;
|
||||
testRunId?: string;
|
||||
refresh?: boolean;
|
||||
skipDetails?: boolean;
|
||||
}) => {
|
||||
const { refreshTimer, lastRefresh } = useTickTick(3 * 1000, refresh);
|
||||
|
||||
const [checkGroupId, setCheckGroupId] = useState('');
|
||||
const [stepEnds, setStepEnds] = useState<JourneyStep[]>([]);
|
||||
const [summary, setSummary] = useState<JourneyStep>();
|
||||
|
||||
const { data, loading } = useBrowserEsResults({ monitorId, lastRefresh });
|
||||
const { data, loading } = useBrowserEsResults({ configId, testRunId, lastRefresh });
|
||||
|
||||
const { data: stepListData } = useFetcher(() => {
|
||||
if (checkGroupId) {
|
||||
if (checkGroupId && !skipDetails) {
|
||||
return fetchJourneySteps({
|
||||
checkGroup: checkGroupId,
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ interface Props {
|
|||
}
|
||||
export function SimpleTestResults({ monitorId }: Props) {
|
||||
const [summaryDocs, setSummaryDocs] = useState<Ping[]>([]);
|
||||
const { summaryDoc, loading } = useSimpleRunOnceMonitors({ monitorId });
|
||||
const { summaryDoc, loading } = useSimpleRunOnceMonitors({ configId: monitorId });
|
||||
|
||||
useEffect(() => {
|
||||
if (summaryDoc) {
|
||||
|
|
|
@ -12,8 +12,14 @@ import { Ping } from '../../../../../common/runtime_types';
|
|||
import { createEsParams, useEsSearch } from '../../../../../../observability/public';
|
||||
import { useTickTick } from '../use_tick_tick';
|
||||
|
||||
export const useSimpleRunOnceMonitors = ({ monitorId }: { monitorId: string }) => {
|
||||
const { refreshTimer, lastRefresh } = useTickTick();
|
||||
export const useSimpleRunOnceMonitors = ({
|
||||
configId,
|
||||
testRunId,
|
||||
}: {
|
||||
configId: string;
|
||||
testRunId?: string;
|
||||
}) => {
|
||||
const { refreshTimer, lastRefresh } = useTickTick(2 * 1000, false);
|
||||
|
||||
const { settings } = useSelector(selectDynamicSettings);
|
||||
|
||||
|
@ -31,7 +37,7 @@ export const useSimpleRunOnceMonitors = ({ monitorId }: { monitorId: string }) =
|
|||
filter: [
|
||||
{
|
||||
term: {
|
||||
config_id: monitorId,
|
||||
config_id: configId,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -39,13 +45,22 @@ export const useSimpleRunOnceMonitors = ({ monitorId }: { monitorId: string }) =
|
|||
field: 'summary',
|
||||
},
|
||||
},
|
||||
...(testRunId
|
||||
? [
|
||||
{
|
||||
term: {
|
||||
test_run_id: testRunId,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
size: 10,
|
||||
}),
|
||||
[monitorId, settings?.heartbeatIndices, lastRefresh],
|
||||
[configId, settings?.heartbeatIndices, lastRefresh],
|
||||
{ name: 'TestRunData' }
|
||||
);
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ export function TestResultHeader({ doc, title, summaryDocs, journeyStarted, isCo
|
|||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge style={{ width: 100 }} color={journeyStarted ? 'primary' : 'warning'}>
|
||||
{journeyStarted ? IN_PROGRESS : PENDING_LABEL}
|
||||
{journeyStarted ? IN_PROGRESS_LABEL : PENDING_LABEL}
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -86,7 +86,7 @@ export function TestResultHeader({ doc, title, summaryDocs, journeyStarted, isCo
|
|||
);
|
||||
}
|
||||
|
||||
const PENDING_LABEL = i18n.translate('xpack.uptime.monitorManagement.pending', {
|
||||
export const PENDING_LABEL = i18n.translate('xpack.uptime.monitorManagement.pending', {
|
||||
defaultMessage: 'PENDING',
|
||||
});
|
||||
|
||||
|
@ -98,7 +98,7 @@ const COMPLETED_LABEL = i18n.translate('xpack.uptime.monitorManagement.completed
|
|||
defaultMessage: 'COMPLETED',
|
||||
});
|
||||
|
||||
const IN_PROGRESS = i18n.translate('xpack.uptime.monitorManagement.inProgress', {
|
||||
export const IN_PROGRESS_LABEL = i18n.translate('xpack.uptime.monitorManagement.inProgress', {
|
||||
defaultMessage: 'IN PROGRESS',
|
||||
});
|
||||
|
||||
|
|
|
@ -8,13 +8,18 @@
|
|||
import { useEffect, useState, useContext } from 'react';
|
||||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
|
||||
export function useTickTick() {
|
||||
const { refreshApp, lastRefresh } = useContext(UptimeRefreshContext);
|
||||
export function useTickTick(interval?: number, refresh = true) {
|
||||
const { refreshApp } = useContext(UptimeRefreshContext);
|
||||
|
||||
const [nextTick, setNextTick] = useState(Date.now());
|
||||
|
||||
const [tickTick] = useState<NodeJS.Timer>(() =>
|
||||
setInterval(() => {
|
||||
refreshApp();
|
||||
}, 5 * 1000)
|
||||
if (refresh) {
|
||||
refreshApp();
|
||||
}
|
||||
setNextTick(Date.now());
|
||||
}, interval ?? 5 * 1000)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -23,5 +28,5 @@ export function useTickTick() {
|
|||
};
|
||||
}, [tickTick]);
|
||||
|
||||
return { refreshTimer: tickTick, lastRefresh };
|
||||
return { refreshTimer: tickTick, lastRefresh: nextTick };
|
||||
}
|
||||
|
|
|
@ -229,7 +229,12 @@ describe('MonitorListStatusColumn', () => {
|
|||
it('provides expected tooltip and display times', async () => {
|
||||
const { getByText } = render(
|
||||
<EuiThemeProvider darkMode={false}>
|
||||
<MonitorListStatusColumn status="up" timestamp="2314123" summaryPings={[]} />
|
||||
<MonitorListStatusColumn
|
||||
status="up"
|
||||
timestamp="2314123"
|
||||
summaryPings={[]}
|
||||
monitorType="http"
|
||||
/>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
|
@ -244,7 +249,12 @@ describe('MonitorListStatusColumn', () => {
|
|||
it('can handle a non-numeric timestamp value', () => {
|
||||
const { getByText } = render(
|
||||
<EuiThemeProvider darkMode={false}>
|
||||
<MonitorListStatusColumn status="up" timestamp={new Date().toString()} summaryPings={[]} />
|
||||
<MonitorListStatusColumn
|
||||
status="up"
|
||||
timestamp={new Date().toString()}
|
||||
summaryPings={[]}
|
||||
monitorType="http"
|
||||
/>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
||||
|
@ -259,6 +269,7 @@ describe('MonitorListStatusColumn', () => {
|
|||
<MonitorListStatusColumn
|
||||
status="up"
|
||||
timestamp={new Date().toString()}
|
||||
monitorType="http"
|
||||
summaryPings={summaryPings.filter((ping) => ping.observer!.geo!.name! === 'Islamabad')}
|
||||
/>
|
||||
</EuiThemeProvider>
|
||||
|
@ -278,6 +289,7 @@ describe('MonitorListStatusColumn', () => {
|
|||
status="up"
|
||||
timestamp={new Date().toString()}
|
||||
summaryPings={summaryPings}
|
||||
monitorType="http"
|
||||
/>
|
||||
</EuiThemeProvider>
|
||||
);
|
||||
|
@ -295,6 +307,7 @@ describe('MonitorListStatusColumn', () => {
|
|||
<EuiThemeProvider darkMode={false}>
|
||||
<MonitorListStatusColumn
|
||||
status="up"
|
||||
monitorType="http"
|
||||
timestamp={new Date().toString()}
|
||||
summaryPings={summaryPings}
|
||||
/>
|
||||
|
|
|
@ -5,13 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import moment from 'moment';
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import styled from 'styled-components';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, EuiBadge, EuiSpacer } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiToolTip,
|
||||
EuiBadge,
|
||||
EuiSpacer,
|
||||
EuiHighlight,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { parseTimestamp } from '../parse_timestamp';
|
||||
import { Ping } from '../../../../../common/runtime_types';
|
||||
import { DataStream, Ping } from '../../../../../common/runtime_types';
|
||||
import {
|
||||
STATUS,
|
||||
SHORT_TIMESPAN_LOCALE,
|
||||
|
@ -22,10 +32,18 @@ import {
|
|||
import { UptimeThemeContext } from '../../../../contexts';
|
||||
import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
|
||||
import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../../../common/translations';
|
||||
import { MonitorProgress } from './progress/monitor_progress';
|
||||
import { refreshedMonitorSelector } from '../../../../state/reducers/monitor_list';
|
||||
import { testNowRunSelector } from '../../../../state/reducers/test_now_runs';
|
||||
import { clearTestNowMonitorAction } from '../../../../state/actions';
|
||||
|
||||
interface MonitorListStatusColumnProps {
|
||||
configId?: string;
|
||||
monitorId?: string;
|
||||
status: string;
|
||||
monitorType: string;
|
||||
timestamp: string;
|
||||
duration?: number;
|
||||
summaryPings: Ping[];
|
||||
}
|
||||
|
||||
|
@ -63,7 +81,7 @@ export const getShortTimeStamp = (timeStamp: moment.Moment, relative = false) =>
|
|||
shortTimestamp = timeStamp.fromNow();
|
||||
}
|
||||
|
||||
// Reset it so, it does't impact other part of the app
|
||||
// Reset it so, it doesn't impact other part of the app
|
||||
moment.locale(prevLocale);
|
||||
return shortTimestamp;
|
||||
} else {
|
||||
|
@ -144,7 +162,11 @@ export const getLocationStatus = (summaryPings: Ping[], status: string) => {
|
|||
};
|
||||
|
||||
export const MonitorListStatusColumn = ({
|
||||
monitorType,
|
||||
configId,
|
||||
monitorId,
|
||||
status,
|
||||
duration,
|
||||
summaryPings = [],
|
||||
timestamp: tsString,
|
||||
}: MonitorListStatusColumnProps) => {
|
||||
|
@ -156,16 +178,39 @@ export const MonitorListStatusColumn = ({
|
|||
|
||||
const { statusMessage, locTooltip } = getLocationStatus(summaryPings, status);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const stopProgressTrack = useCallback(() => {
|
||||
if (configId) {
|
||||
dispatch(clearTestNowMonitorAction(configId));
|
||||
}
|
||||
}, [configId, dispatch]);
|
||||
|
||||
const refreshedMonitorIds = useSelector(refreshedMonitorSelector);
|
||||
|
||||
const testNowRun = useSelector(testNowRunSelector(configId));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<StatusColumnFlexG alignItems="center" gutterSize="none" wrap={false} responsive={false}>
|
||||
<StatusColumnFlexG alignItems="center" gutterSize="xs" wrap={false} responsive={false}>
|
||||
<EuiFlexItem grow={false} style={{ flexBasis: 40 }}>
|
||||
<EuiBadge
|
||||
className="eui-textCenter"
|
||||
color={status === STATUS.UP ? 'success' : dangerBehindText}
|
||||
>
|
||||
{getHealthMessage(status)}
|
||||
</EuiBadge>
|
||||
{testNowRun && configId && testNowRun?.testRunId ? (
|
||||
<MonitorProgress
|
||||
monitorId={monitorId!}
|
||||
configId={configId}
|
||||
testRunId={testNowRun?.testRunId}
|
||||
monitorType={monitorType as DataStream}
|
||||
duration={duration ?? 0}
|
||||
stopProgressTrack={stopProgressTrack}
|
||||
/>
|
||||
) : (
|
||||
<EuiBadge
|
||||
className="eui-textCenter"
|
||||
color={status === STATUS.UP ? 'success' : dangerBehindText}
|
||||
>
|
||||
{getHealthMessage(status)}
|
||||
</EuiBadge>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</StatusColumnFlexG>
|
||||
<EuiSpacer size="xs" />
|
||||
|
@ -183,20 +228,39 @@ export const MonitorListStatusColumn = ({
|
|||
</EuiToolTip>
|
||||
<EuiToolTip
|
||||
content={
|
||||
<EuiText color="ghost" size="xs">
|
||||
{timestamp.toLocaleString()}
|
||||
</EuiText>
|
||||
<>
|
||||
<EuiText color="text" size="xs">
|
||||
<strong> {timestamp.fromNow()}</strong>
|
||||
</EuiText>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiText color="ghost" size="xs">
|
||||
{timestamp.toLocaleString()}
|
||||
</EuiText>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<EuiText size="xs" color="subdued" className="eui-textNoWrap">
|
||||
Checked {getShortTimeStamp(timestamp)}
|
||||
</EuiText>
|
||||
{monitorId && refreshedMonitorIds?.includes(monitorId) ? (
|
||||
<EuiHighlight highlightAll={true} search={getCheckedLabel(timestamp)}>
|
||||
{getCheckedLabel(timestamp)}
|
||||
</EuiHighlight>
|
||||
) : (
|
||||
<EuiText size="xs" color="subdued" className="eui-textNoWrap">
|
||||
{getCheckedLabel(timestamp)}
|
||||
</EuiText>
|
||||
)}
|
||||
</EuiToolTip>
|
||||
</EuiText>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getCheckedLabel = (timestamp: Moment) => {
|
||||
return i18n.translate('xpack.uptime.monitorList.statusColumn.checkedTimestamp', {
|
||||
defaultMessage: 'Checked {timestamp}',
|
||||
values: { timestamp: getShortTimeStamp(timestamp) },
|
||||
});
|
||||
};
|
||||
|
||||
const PaddedText = euiStyled(EuiText)`
|
||||
padding-right: ${(props) => props.theme.eui.paddingSizes.xs};
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 { EuiBadge, EuiProgress } from '@elastic/eui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useBrowserRunOnceMonitors } from '../../../../monitor_management/test_now_mode/browser/use_browser_run_once_monitors';
|
||||
import {
|
||||
IN_PROGRESS_LABEL,
|
||||
PENDING_LABEL,
|
||||
} from '../../../../monitor_management/test_now_mode/test_result_header';
|
||||
|
||||
export const BrowserMonitorProgress = ({
|
||||
configId,
|
||||
testRunId,
|
||||
duration,
|
||||
isUpdating,
|
||||
updateMonitorStatus,
|
||||
}: {
|
||||
configId: string;
|
||||
testRunId: string;
|
||||
duration: number;
|
||||
isUpdating: boolean;
|
||||
updateMonitorStatus: () => void;
|
||||
}) => {
|
||||
const { journeyStarted, summaryDoc, data } = useBrowserRunOnceMonitors({
|
||||
configId,
|
||||
testRunId,
|
||||
refresh: false,
|
||||
skipDetails: true,
|
||||
});
|
||||
|
||||
const [startTime, setStartTime] = useState(Date.now());
|
||||
const [passedTime, setPassedTime] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (summaryDoc) {
|
||||
updateMonitorStatus();
|
||||
}
|
||||
}, [updateMonitorStatus, summaryDoc]);
|
||||
|
||||
useEffect(() => {
|
||||
const interVal = setInterval(() => {
|
||||
if (journeyStarted) {
|
||||
setPassedTime((Date.now() - startTime) * 1000);
|
||||
}
|
||||
}, 500);
|
||||
const startTimeValue = startTime;
|
||||
return () => {
|
||||
if ((Date.now() - startTimeValue) * 1000 > duration) {
|
||||
clearInterval(interVal);
|
||||
}
|
||||
};
|
||||
}, [data, duration, journeyStarted, startTime]);
|
||||
|
||||
useEffect(() => {
|
||||
if (journeyStarted) {
|
||||
setStartTime(Date.now());
|
||||
}
|
||||
}, [journeyStarted]);
|
||||
|
||||
if (isUpdating || passedTime > duration) {
|
||||
return (
|
||||
<>
|
||||
<EuiBadge>{IN_PROGRESS_LABEL}</EuiBadge>
|
||||
<EuiProgress size="xs" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{journeyStarted ? (
|
||||
<>
|
||||
<EuiBadge>{IN_PROGRESS_LABEL}</EuiBadge>
|
||||
<EuiProgress value={passedTime} max={duration} size="xs" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EuiBadge>{PENDING_LABEL}</EuiBadge>
|
||||
<EuiProgress size="xs" />
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
|
@ -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 React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SimpleMonitorProgress } from './simple_monitor_progress';
|
||||
import { BrowserMonitorProgress } from './browser_monitor_progress';
|
||||
import { DataStream } from '../../../../../../common/runtime_types';
|
||||
import { useUpdatedMonitor } from './use_updated_monitor';
|
||||
import { refreshedMonitorSelector } from '../../../../../state/reducers/monitor_list';
|
||||
|
||||
export const MonitorProgress = ({
|
||||
monitorId,
|
||||
configId,
|
||||
testRunId,
|
||||
duration,
|
||||
monitorType,
|
||||
stopProgressTrack,
|
||||
}: {
|
||||
monitorId: string;
|
||||
configId: string;
|
||||
testRunId: string;
|
||||
duration: number;
|
||||
monitorType: DataStream;
|
||||
stopProgressTrack: () => void;
|
||||
}) => {
|
||||
const { updateMonitorStatus, isUpdating } = useUpdatedMonitor({
|
||||
testRunId,
|
||||
monitorId,
|
||||
});
|
||||
|
||||
const refreshedMonitorId = useSelector(refreshedMonitorSelector);
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshedMonitorId.includes(monitorId)) {
|
||||
stopProgressTrack();
|
||||
}
|
||||
}, [isUpdating, monitorId, refreshedMonitorId, stopProgressTrack]);
|
||||
|
||||
return monitorType === 'browser' ? (
|
||||
<BrowserMonitorProgress
|
||||
configId={configId}
|
||||
testRunId={testRunId}
|
||||
duration={duration}
|
||||
isUpdating={isUpdating}
|
||||
updateMonitorStatus={updateMonitorStatus}
|
||||
/>
|
||||
) : (
|
||||
<SimpleMonitorProgress
|
||||
monitorId={monitorId}
|
||||
testRunId={testRunId}
|
||||
duration={duration}
|
||||
isUpdating={isUpdating}
|
||||
updateMonitorStatus={updateMonitorStatus}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { EuiBadge, EuiProgress } from '@elastic/eui';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useSimpleRunOnceMonitors } from '../../../../monitor_management/test_now_mode/simple/use_simple_run_once_monitors';
|
||||
import { IN_PROGRESS_LABEL } from '../../../../monitor_management/test_now_mode/test_result_header';
|
||||
|
||||
export const SimpleMonitorProgress = ({
|
||||
monitorId,
|
||||
testRunId,
|
||||
duration,
|
||||
isUpdating,
|
||||
updateMonitorStatus,
|
||||
}: {
|
||||
monitorId: string;
|
||||
testRunId: string;
|
||||
duration: number;
|
||||
isUpdating: boolean;
|
||||
updateMonitorStatus: () => void;
|
||||
}) => {
|
||||
const { summaryDoc, data } = useSimpleRunOnceMonitors({
|
||||
configId: monitorId,
|
||||
testRunId,
|
||||
});
|
||||
|
||||
const startTime = useRef(Date.now());
|
||||
|
||||
const [passedTime, setPassedTime] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (summaryDoc) {
|
||||
updateMonitorStatus();
|
||||
}
|
||||
}, [updateMonitorStatus, summaryDoc]);
|
||||
|
||||
useEffect(() => {
|
||||
setPassedTime(Date.now() - startTime.current);
|
||||
}, [data]);
|
||||
|
||||
const passedTimeMicro = passedTime * 1000;
|
||||
|
||||
if (isUpdating || passedTimeMicro > duration) {
|
||||
return (
|
||||
<>
|
||||
<EuiBadge>{IN_PROGRESS_LABEL}</EuiBadge>
|
||||
<EuiProgress size="xs" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
<EuiBadge>{IN_PROGRESS_LABEL}</EuiBadge>
|
||||
<EuiProgress value={passedTimeMicro} max={duration} size="xs" />
|
||||
</span>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getUpdatedMonitor, setUpdatingMonitorId } from '../../../../../state/actions';
|
||||
import { isUpdatingMonitorSelector } from '../../../../../state/reducers/monitor_list';
|
||||
|
||||
export const useUpdatedMonitor = ({
|
||||
testRunId,
|
||||
monitorId,
|
||||
}: {
|
||||
testRunId: string;
|
||||
monitorId: string;
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isUpdatingMonitors = useSelector(isUpdatingMonitorSelector);
|
||||
|
||||
const updateMonitorStatus = useCallback(() => {
|
||||
if (testRunId) {
|
||||
dispatch(
|
||||
getUpdatedMonitor.get({
|
||||
dateRangeStart: 'now-10m',
|
||||
dateRangeEnd: 'now',
|
||||
filters: JSON.stringify({
|
||||
bool: {
|
||||
should: [{ match_phrase: { test_run_id: testRunId } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
}),
|
||||
pageSize: 1,
|
||||
})
|
||||
);
|
||||
dispatch(setUpdatingMonitorId(monitorId));
|
||||
}
|
||||
}, [dispatch, monitorId, testRunId]);
|
||||
|
||||
return { updateMonitorStatus, isUpdating: isUpdatingMonitors.includes(monitorId) };
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { EuiButtonIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
|
||||
import { testNowMonitorAction } from '../../../../state/actions';
|
||||
import { testNowRunSelector } from '../../../../state/reducers/test_now_runs';
|
||||
|
||||
export const TestNowColumn = ({
|
||||
monitorId,
|
||||
configId,
|
||||
}: {
|
||||
monitorId: string;
|
||||
configId?: string;
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const testNowRun = useSelector(testNowRunSelector(configId));
|
||||
|
||||
if (!configId) {
|
||||
return <>--</>;
|
||||
}
|
||||
|
||||
const testNowClick = () => {
|
||||
dispatch(testNowMonitorAction.get(configId));
|
||||
};
|
||||
|
||||
if (testNowRun && testNowRun.status === 'loading') {
|
||||
return <EuiLoadingSpinner size="s" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip content={testNowRun ? TEST_SCHEDULED_LABEL : TEST_NOW_LABEL}>
|
||||
<EuiButtonIcon
|
||||
iconType="play"
|
||||
onClick={() => testNowClick()}
|
||||
isDisabled={Boolean(testNowRun)}
|
||||
aria-label={TEST_NOW_ARIA_LABEL}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
||||
export const TEST_NOW_ARIA_LABEL = i18n.translate('xpack.uptime.monitorList.testNow.AriaLabel', {
|
||||
defaultMessage: 'CLick to run test now',
|
||||
});
|
||||
|
||||
export const TEST_NOW_LABEL = i18n.translate('xpack.uptime.monitorList.testNow.label', {
|
||||
defaultMessage: 'Test now',
|
||||
});
|
||||
|
||||
export const TEST_SCHEDULED_LABEL = i18n.translate('xpack.uptime.monitorList.testNow.scheduled', {
|
||||
defaultMessage: 'Test is already scheduled',
|
||||
});
|
|
@ -16,7 +16,6 @@ import {
|
|||
MonitorSummary,
|
||||
} from '../../../../common/runtime_types';
|
||||
import { MonitorListComponent, noItemsMessage } from './monitor_list';
|
||||
import * as redux from 'react-redux';
|
||||
import moment from 'moment';
|
||||
import { IHttpFetchError, ResponseErrorBody } from '../../../../../../../src/core/public';
|
||||
import { mockMoment } from '../../../lib/helper/test_helpers';
|
||||
|
@ -56,7 +55,7 @@ const testFooPings: Ping[] = [
|
|||
const testFooSummary: MonitorSummary = {
|
||||
monitor_id: 'foo',
|
||||
state: {
|
||||
monitor: { type: 'http' },
|
||||
monitor: { type: 'http', duration: { us: 1000 } },
|
||||
summaryPings: testFooPings,
|
||||
summary: {
|
||||
up: 1,
|
||||
|
@ -91,7 +90,7 @@ const testBarPings: Ping[] = [
|
|||
const testBarSummary: MonitorSummary = {
|
||||
monitor_id: 'bar',
|
||||
state: {
|
||||
monitor: { type: 'http' },
|
||||
monitor: { type: 'http', duration: { us: 1000 } },
|
||||
summaryPings: testBarPings,
|
||||
summary: {
|
||||
up: 2,
|
||||
|
@ -129,12 +128,6 @@ describe('MonitorList component', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const useDispatchSpy = jest.spyOn(redux, 'useDispatch');
|
||||
useDispatchSpy.mockReturnValue(jest.fn());
|
||||
|
||||
const useSelectorSpy = jest.spyOn(redux, 'useSelector');
|
||||
useSelectorSpy.mockReturnValue(true);
|
||||
|
||||
localStorageMock = {
|
||||
getItem: jest.fn().mockImplementation(() => '25'),
|
||||
setItem: jest.fn(),
|
||||
|
@ -156,6 +149,7 @@ describe('MonitorList component', () => {
|
|||
}}
|
||||
pageSize={10}
|
||||
setPageSize={jest.fn()}
|
||||
refreshedMonitorIds={[]}
|
||||
/>
|
||||
);
|
||||
expect(await findByText(NO_DATA_MESSAGE)).toBeInTheDocument();
|
||||
|
@ -170,6 +164,7 @@ describe('MonitorList component', () => {
|
|||
}}
|
||||
pageSize={10}
|
||||
setPageSize={jest.fn()}
|
||||
refreshedMonitorIds={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -190,6 +185,7 @@ describe('MonitorList component', () => {
|
|||
}}
|
||||
pageSize={10}
|
||||
setPageSize={jest.fn()}
|
||||
refreshedMonitorIds={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -226,6 +222,7 @@ describe('MonitorList component', () => {
|
|||
}}
|
||||
pageSize={10}
|
||||
setPageSize={jest.fn()}
|
||||
refreshedMonitorIds={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -254,6 +251,7 @@ describe('MonitorList component', () => {
|
|||
}}
|
||||
pageSize={10}
|
||||
setPageSize={jest.fn()}
|
||||
refreshedMonitorIds={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -283,6 +281,7 @@ describe('MonitorList component', () => {
|
|||
}}
|
||||
pageSize={10}
|
||||
setPageSize={jest.fn()}
|
||||
refreshedMonitorIds={[]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -32,15 +32,19 @@ import { CertStatusColumn } from './columns/cert_status_column';
|
|||
import { MonitorListHeader } from './monitor_list_header';
|
||||
import { TAGS_LABEL, URL_LABEL } from '../../common/translations';
|
||||
import { EnableMonitorAlert } from './columns/enable_alert';
|
||||
import { STATUS_ALERT_COLUMN } from './translations';
|
||||
import { STATUS_ALERT_COLUMN, TEST_NOW_COLUMN } from './translations';
|
||||
import { MonitorNameColumn } from './columns/monitor_name_col';
|
||||
import { MonitorTags } from '../../common/monitor_tags';
|
||||
import { useMonitorHistogram } from './use_monitor_histogram';
|
||||
import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common';
|
||||
import { TestNowColumn } from './columns/test_now_col';
|
||||
import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context';
|
||||
|
||||
interface Props extends MonitorListProps {
|
||||
pageSize: number;
|
||||
setPageSize: (val: number) => void;
|
||||
monitorList: MonitorList;
|
||||
refreshedMonitorIds: string[];
|
||||
}
|
||||
|
||||
export const noItemsMessage = (loading: boolean, filters?: string) => {
|
||||
|
@ -52,8 +56,15 @@ export const MonitorListComponent: ({
|
|||
filters,
|
||||
monitorList: { list, error, loading },
|
||||
pageSize,
|
||||
refreshedMonitorIds,
|
||||
setPageSize,
|
||||
}: Props) => any = ({ filters, monitorList: { list, error, loading }, pageSize, setPageSize }) => {
|
||||
}: Props) => any = ({
|
||||
filters,
|
||||
refreshedMonitorIds = [],
|
||||
monitorList: { list, error, loading },
|
||||
pageSize,
|
||||
setPageSize,
|
||||
}) => {
|
||||
const [expandedDrawerIds, updateExpandedDrawerIds] = useState<string[]>([]);
|
||||
const { width } = useWindowSize();
|
||||
const [hideExtraColumns, setHideExtraColumns] = useState(false);
|
||||
|
@ -94,6 +105,8 @@ export const MonitorListComponent: ({
|
|||
}, {});
|
||||
};
|
||||
|
||||
const { config } = useUptimeSettingsContext();
|
||||
|
||||
const columns = [
|
||||
...[
|
||||
{
|
||||
|
@ -103,12 +116,27 @@ export const MonitorListComponent: ({
|
|||
mobileOptions: {
|
||||
fullWidth: true,
|
||||
},
|
||||
render: (status: string, { state: { timestamp, summaryPings } }: MonitorSummary) => {
|
||||
render: (
|
||||
status: string,
|
||||
{
|
||||
monitor_id: monitorId,
|
||||
state: {
|
||||
timestamp,
|
||||
summaryPings,
|
||||
monitor: { type, duration },
|
||||
},
|
||||
configId,
|
||||
}: MonitorSummary
|
||||
) => {
|
||||
return (
|
||||
<MonitorListStatusColumn
|
||||
configId={configId}
|
||||
status={status}
|
||||
timestamp={timestamp}
|
||||
summaryPings={summaryPings ?? []}
|
||||
monitorType={type}
|
||||
duration={duration!.us}
|
||||
monitorId={monitorId}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
@ -166,20 +194,31 @@ export const MonitorListComponent: ({
|
|||
},
|
||||
]
|
||||
: []),
|
||||
...[
|
||||
{
|
||||
align: 'center' as const,
|
||||
field: '',
|
||||
name: STATUS_ALERT_COLUMN,
|
||||
width: '100px',
|
||||
render: (item: MonitorSummary) => (
|
||||
<EnableMonitorAlert
|
||||
monitorId={item.monitor_id}
|
||||
selectedMonitor={item.state.summaryPings[0]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
{
|
||||
align: 'center' as const,
|
||||
field: '',
|
||||
name: STATUS_ALERT_COLUMN,
|
||||
width: '100px',
|
||||
render: (item: MonitorSummary) => (
|
||||
<EnableMonitorAlert
|
||||
monitorId={item.monitor_id}
|
||||
selectedMonitor={item.state.summaryPings[0]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(config.ui?.monitorManagement?.enabled
|
||||
? [
|
||||
{
|
||||
align: 'center' as const,
|
||||
field: '',
|
||||
name: TEST_NOW_COLUMN,
|
||||
width: '100px',
|
||||
render: (item: MonitorSummary) => (
|
||||
<TestNowColumn monitorId={item.monitor_id} configId={item.configId} />
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!hideExtraColumns
|
||||
? [
|
||||
{
|
||||
|
@ -205,7 +244,7 @@ export const MonitorListComponent: ({
|
|||
];
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
<WrapperPanel hasBorder>
|
||||
<MonitorListHeader />
|
||||
<EuiSpacer size="m" />
|
||||
<EuiBasicTable
|
||||
|
@ -226,7 +265,9 @@ export const MonitorListComponent: ({
|
|||
onClick: () => toggleDrawer(monitorId),
|
||||
'aria-label': labels.getExpandDrawerLabel(monitorId),
|
||||
})
|
||||
: undefined
|
||||
: ({ monitor_id: monitorId }) => ({
|
||||
className: refreshedMonitorIds.includes(monitorId) ? 'refresh-row' : undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
@ -253,6 +294,17 @@ export const MonitorListComponent: ({
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</WrapperPanel>
|
||||
);
|
||||
};
|
||||
|
||||
const WrapperPanel = euiStyled(EuiPanel)`
|
||||
&&& {
|
||||
.refresh-row{
|
||||
background-color: #f0f4fb;
|
||||
-webkit-transition: background-color 3000ms linear;
|
||||
-ms-transition: background-color 3000ms linear;
|
||||
transition: background-color 3000ms linear;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { getMonitorList } from '../../../state/actions';
|
||||
import { clearRefreshedMonitorId, getMonitorList } from '../../../state/actions';
|
||||
import { esKuerySelector, monitorListSelector } from '../../../state/selectors';
|
||||
import { MonitorListComponent } from './monitor_list';
|
||||
import { useUrlParams } from '../../../hooks';
|
||||
|
@ -15,6 +15,7 @@ import { UptimeRefreshContext } from '../../../contexts';
|
|||
import { getConnectorsAction, getMonitorAlertsAction } from '../../../state/alerts/alerts';
|
||||
import { useMappingCheck } from '../../../hooks/use_mapping_check';
|
||||
import { useOverviewFilterCheck } from '../../../hooks/use_overview_filter_check';
|
||||
import { refreshedMonitorSelector } from '../../../state/reducers/monitor_list';
|
||||
|
||||
export interface MonitorListProps {
|
||||
filters?: string;
|
||||
|
@ -43,6 +44,8 @@ export const MonitorList: React.FC<MonitorListProps> = (props) => {
|
|||
|
||||
const { lastRefresh } = useContext(UptimeRefreshContext);
|
||||
|
||||
const refreshedMonitorIds = useSelector(refreshedMonitorSelector);
|
||||
|
||||
const monitorList = useSelector(monitorListSelector);
|
||||
useMappingCheck(monitorList.error);
|
||||
|
||||
|
@ -81,12 +84,23 @@ export const MonitorList: React.FC<MonitorListProps> = (props) => {
|
|||
dispatch(getConnectorsAction.get());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (refreshedMonitorIds) {
|
||||
refreshedMonitorIds.forEach((id) => {
|
||||
setTimeout(() => {
|
||||
dispatch(clearRefreshedMonitorId(id));
|
||||
}, 5 * 1000);
|
||||
});
|
||||
}
|
||||
}, [dispatch, refreshedMonitorIds]);
|
||||
|
||||
return (
|
||||
<MonitorListComponent
|
||||
{...props}
|
||||
monitorList={monitorList}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
refreshedMonitorIds={refreshedMonitorIds}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -73,3 +73,7 @@ export const RESPONSE_ANOMALY_SCORE = i18n.translate(
|
|||
export const STATUS_ALERT_COLUMN = i18n.translate('xpack.uptime.monitorList.statusAlert.label', {
|
||||
defaultMessage: 'Status alert',
|
||||
});
|
||||
|
||||
export const TEST_NOW_COLUMN = i18n.translate('xpack.uptime.monitorList.testNow.label', {
|
||||
defaultMessage: 'Test now',
|
||||
});
|
||||
|
|
|
@ -59,6 +59,7 @@ export const mockState: AppState = {
|
|||
summaries: [],
|
||||
},
|
||||
loading: false,
|
||||
refreshedMonitorIds: [],
|
||||
},
|
||||
monitorManagementList: {
|
||||
list: {
|
||||
|
@ -115,4 +116,7 @@ export const mockState: AppState = {
|
|||
cacheSize: 0,
|
||||
hitCount: [],
|
||||
},
|
||||
testNowRuns: {
|
||||
testNowRuns: [],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,9 +7,25 @@
|
|||
|
||||
import { createAction } from 'redux-actions';
|
||||
import { FetchMonitorStatesQueryArgs, MonitorSummariesResult } from '../../../common/runtime_types';
|
||||
import { createAsyncAction } from './utils';
|
||||
import { TestNowResponse } from '../api';
|
||||
|
||||
export const getMonitorList = createAction<FetchMonitorStatesQueryArgs>('GET_MONITOR_LIST');
|
||||
export const getMonitorListSuccess = createAction<MonitorSummariesResult>(
|
||||
'GET_MONITOR_LIST_SUCCESS'
|
||||
);
|
||||
export const getMonitorListFailure = createAction<Error>('GET_MONITOR_LIST_FAIL');
|
||||
|
||||
export const setUpdatingMonitorId = createAction<string>('SET_UPDATING_MONITOR_ID');
|
||||
export const clearRefreshedMonitorId = createAction<string>('CLEAR_REFRESH_MONITOR_ID');
|
||||
|
||||
export const testNowMonitorAction = createAsyncAction<string, TestNowResponse | undefined>(
|
||||
'TEST_NOW_MONITOR_ACTION'
|
||||
);
|
||||
|
||||
export const clearTestNowMonitorAction = createAction<string>('CLEAR_TEST_NOW_MONITOR_ACTION');
|
||||
|
||||
export const getUpdatedMonitor = createAsyncAction<
|
||||
FetchMonitorStatesQueryArgs,
|
||||
MonitorSummariesResult
|
||||
>('GET_UPDATED_MONITOR');
|
||||
|
|
|
@ -67,3 +67,13 @@ export const runOnceMonitor = async ({
|
|||
}): Promise<{ errors: Array<{ error: Error }> }> => {
|
||||
return await apiService.post(API_URLS.RUN_ONCE_MONITOR + `/${id}`, monitor);
|
||||
};
|
||||
|
||||
export interface TestNowResponse {
|
||||
errors?: Array<{ error: Error }>;
|
||||
testRunId: string;
|
||||
monitorId: string;
|
||||
}
|
||||
|
||||
export const testNowMonitor = async (configId: string): Promise<TestNowResponse | undefined> => {
|
||||
return await apiService.get(API_URLS.TRIGGER_MONITOR + `/${configId}`);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
import { fork } from 'redux-saga/effects';
|
||||
import { fetchMonitorDetailsEffect } from './monitor';
|
||||
import { fetchMonitorListEffect } from './monitor_list';
|
||||
import {
|
||||
fetchMonitorListEffect,
|
||||
fetchRunNowMonitorEffect,
|
||||
fetchUpdatedMonitorEffect,
|
||||
} from './monitor_list';
|
||||
import { fetchMonitorManagementEffect } from './monitor_management';
|
||||
import { fetchMonitorStatusEffect } from './monitor_status';
|
||||
import { fetchDynamicSettingsEffect, setDynamicSettingsEffect } from './dynamic_settings';
|
||||
|
@ -27,6 +31,7 @@ import {
|
|||
export function* rootEffect() {
|
||||
yield fork(fetchMonitorDetailsEffect);
|
||||
yield fork(fetchMonitorListEffect);
|
||||
yield fork(fetchUpdatedMonitorEffect);
|
||||
yield fork(fetchMonitorManagementEffect);
|
||||
yield fork(fetchMonitorStatusEffect);
|
||||
yield fork(fetchDynamicSettingsEffect);
|
||||
|
@ -42,4 +47,5 @@ export function* rootEffect() {
|
|||
yield fork(fetchScreenshotBlocks);
|
||||
yield fork(generateBlockStatsOnPut);
|
||||
yield fork(pruneBlockCache);
|
||||
yield fork(fetchRunNowMonitorEffect);
|
||||
}
|
||||
|
|
|
@ -5,9 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { takeLatest } from 'redux-saga/effects';
|
||||
import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions';
|
||||
import { fetchMonitorList } from '../api';
|
||||
import { takeEvery, takeLatest } from 'redux-saga/effects';
|
||||
import {
|
||||
getMonitorList,
|
||||
getMonitorListSuccess,
|
||||
getMonitorListFailure,
|
||||
getUpdatedMonitor,
|
||||
testNowMonitorAction,
|
||||
} from '../actions';
|
||||
import { fetchMonitorList, testNowMonitor } from '../api';
|
||||
import { fetchEffectFactory } from './fetch_effect';
|
||||
|
||||
export function* fetchMonitorListEffect() {
|
||||
|
@ -16,3 +22,17 @@ export function* fetchMonitorListEffect() {
|
|||
fetchEffectFactory(fetchMonitorList, getMonitorListSuccess, getMonitorListFailure)
|
||||
);
|
||||
}
|
||||
|
||||
export function* fetchUpdatedMonitorEffect() {
|
||||
yield takeLatest(
|
||||
getUpdatedMonitor.get,
|
||||
fetchEffectFactory(fetchMonitorList, getUpdatedMonitor.success, getUpdatedMonitor.fail)
|
||||
);
|
||||
}
|
||||
|
||||
export function* fetchRunNowMonitorEffect() {
|
||||
yield takeEvery(
|
||||
testNowMonitorAction.get,
|
||||
fetchEffectFactory(testNowMonitor, testNowMonitorAction.success, testNowMonitorAction.fail)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import { journeyReducer } from './journey';
|
|||
import { networkEventsReducer } from './network_events';
|
||||
import { syntheticsReducer } from './synthetics';
|
||||
import { monitorManagementListReducer } from './monitor_management';
|
||||
import { testNowRunsReducer } from './test_now_runs';
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
monitor: monitorReducer,
|
||||
|
@ -42,4 +43,5 @@ export const rootReducer = combineReducers({
|
|||
journeys: journeyReducer,
|
||||
networkEvents: networkEventsReducer,
|
||||
synthetics: syntheticsReducer,
|
||||
testNowRuns: testNowRunsReducer,
|
||||
});
|
||||
|
|
|
@ -7,13 +7,24 @@
|
|||
|
||||
import { handleActions, Action } from 'redux-actions';
|
||||
import { IHttpFetchError, ResponseErrorBody } from 'src/core/public';
|
||||
import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions';
|
||||
import {
|
||||
getMonitorList,
|
||||
getMonitorListSuccess,
|
||||
getMonitorListFailure,
|
||||
getUpdatedMonitor,
|
||||
clearRefreshedMonitorId,
|
||||
setUpdatingMonitorId,
|
||||
} from '../actions';
|
||||
import { MonitorSummariesResult } from '../../../common/runtime_types';
|
||||
import { AppState } from '../index';
|
||||
import { TestNowResponse } from '../api';
|
||||
|
||||
export interface MonitorList {
|
||||
error?: IHttpFetchError<ResponseErrorBody>;
|
||||
loading: boolean;
|
||||
refreshedMonitorIds?: string[];
|
||||
isUpdating?: string[];
|
||||
list: MonitorSummariesResult;
|
||||
error?: IHttpFetchError<ResponseErrorBody>;
|
||||
}
|
||||
|
||||
export const initialState: MonitorList = {
|
||||
|
@ -23,9 +34,13 @@ export const initialState: MonitorList = {
|
|||
summaries: [],
|
||||
},
|
||||
loading: false,
|
||||
refreshedMonitorIds: [],
|
||||
};
|
||||
|
||||
type Payload = MonitorSummariesResult & IHttpFetchError<ResponseErrorBody>;
|
||||
type Payload = MonitorSummariesResult &
|
||||
IHttpFetchError<ResponseErrorBody> &
|
||||
string &
|
||||
TestNowResponse;
|
||||
|
||||
export const monitorListReducer = handleActions<MonitorList, Payload>(
|
||||
{
|
||||
|
@ -50,6 +65,64 @@ export const monitorListReducer = handleActions<MonitorList, Payload>(
|
|||
error: action.payload,
|
||||
loading: false,
|
||||
}),
|
||||
[String(setUpdatingMonitorId)]: (state: MonitorList, action: Action<string>) => ({
|
||||
...state,
|
||||
isUpdating: [...(state.isUpdating ?? []), action.payload],
|
||||
}),
|
||||
[String(getUpdatedMonitor.get)]: (state: MonitorList) => ({
|
||||
...state,
|
||||
}),
|
||||
[String(getUpdatedMonitor.success)]: (
|
||||
state: MonitorList,
|
||||
action: Action<MonitorSummariesResult>
|
||||
) => {
|
||||
const summaries = state.list.summaries;
|
||||
|
||||
const newSummary = action.payload.summaries?.[0];
|
||||
|
||||
if (!newSummary) {
|
||||
return { ...state, isUpdating: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
isUpdating: state.isUpdating?.filter((item) => item !== newSummary.monitor_id),
|
||||
refreshedMonitorIds: [...(state.refreshedMonitorIds ?? []), newSummary.monitor_id],
|
||||
list: {
|
||||
...state.list,
|
||||
summaries: summaries.map((summary) => {
|
||||
if (summary.monitor_id === newSummary.monitor_id) {
|
||||
return newSummary;
|
||||
}
|
||||
return summary;
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
[String(getUpdatedMonitor.fail)]: (
|
||||
state: MonitorList,
|
||||
action: Action<IHttpFetchError<ResponseErrorBody>>
|
||||
) => ({
|
||||
...state,
|
||||
error: action.payload,
|
||||
loading: false,
|
||||
isUpdating: [],
|
||||
}),
|
||||
[String(clearRefreshedMonitorId)]: (state: MonitorList, action: Action<string>) => ({
|
||||
...state,
|
||||
refreshedMonitorIds: (state.refreshedMonitorIds ?? []).filter(
|
||||
(item) => item !== action.payload
|
||||
),
|
||||
}),
|
||||
},
|
||||
initialState
|
||||
);
|
||||
|
||||
export const refreshedMonitorSelector = ({ monitorList }: AppState) => {
|
||||
return monitorList.refreshedMonitorIds ?? [];
|
||||
};
|
||||
|
||||
export const isUpdatingMonitorSelector = ({ monitorList }: AppState) =>
|
||||
monitorList.isUpdating ?? [];
|
||||
|
|
82
x-pack/plugins/uptime/public/state/reducers/test_now_runs.ts
Normal file
82
x-pack/plugins/uptime/public/state/reducers/test_now_runs.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { createReducer, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { WritableDraft } from 'immer/dist/types/types-external';
|
||||
import { clearTestNowMonitorAction, testNowMonitorAction } from '../actions';
|
||||
import { TestNowResponse } from '../api';
|
||||
import { AppState } from '../index';
|
||||
|
||||
export enum TestRunStats {
|
||||
LOADING = 'loading',
|
||||
IN_PROGRESS = 'in-progress',
|
||||
COMPLETED = 'completed',
|
||||
}
|
||||
|
||||
interface TestNowRun {
|
||||
monitorId: string;
|
||||
testRunId?: string;
|
||||
status: TestRunStats;
|
||||
}
|
||||
|
||||
export interface TestNowRunsState {
|
||||
testNowRuns: TestNowRun[];
|
||||
}
|
||||
|
||||
export const initialState: TestNowRunsState = {
|
||||
testNowRuns: [],
|
||||
};
|
||||
|
||||
export const testNowRunsReducer = createReducer(initialState, (builder) => {
|
||||
builder
|
||||
.addCase(
|
||||
String(testNowMonitorAction.get),
|
||||
(state: WritableDraft<TestNowRunsState>, action: PayloadAction<string>) => ({
|
||||
...state,
|
||||
testNowRuns: [
|
||||
...state.testNowRuns,
|
||||
{ monitorId: action.payload, status: TestRunStats.LOADING },
|
||||
],
|
||||
})
|
||||
)
|
||||
.addCase(
|
||||
String(testNowMonitorAction.success),
|
||||
(state: WritableDraft<TestNowRunsState>, { payload }: PayloadAction<TestNowResponse>) => ({
|
||||
...state,
|
||||
testNowRuns: state.testNowRuns.map((tRun) =>
|
||||
tRun.monitorId === payload.monitorId
|
||||
? {
|
||||
monitorId: payload.monitorId,
|
||||
testRunId: payload.testRunId,
|
||||
status: TestRunStats.IN_PROGRESS,
|
||||
}
|
||||
: tRun
|
||||
),
|
||||
})
|
||||
)
|
||||
.addCase(
|
||||
String(testNowMonitorAction.fail),
|
||||
(state: WritableDraft<TestNowRunsState>, action: PayloadAction<TestNowResponse>) => ({
|
||||
...state,
|
||||
testNowRuns: [...(state.testNowRuns ?? [])],
|
||||
})
|
||||
)
|
||||
.addCase(
|
||||
String(clearTestNowMonitorAction),
|
||||
(state: WritableDraft<TestNowRunsState>, action: PayloadAction<string>) => ({
|
||||
...state,
|
||||
testNowRuns: state.testNowRuns.filter((tRun) => tRun.monitorId !== action.payload),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
export const testNowRunsSelector = ({ testNowRuns }: AppState) => testNowRuns.testNowRuns;
|
||||
|
||||
export const testNowRunSelector =
|
||||
(monitorId?: string) =>
|
||||
({ testNowRuns }: AppState) =>
|
||||
testNowRuns.testNowRuns.find((tRun) => monitorId && monitorId === tRun.monitorId);
|
|
@ -83,11 +83,13 @@ export const summaryPingsToSummary = (summaryPings: Ping[]): MonitorSummary => {
|
|||
const latest = summaryPings[summaryPings.length - 1];
|
||||
return {
|
||||
monitor_id: latest.monitor.id,
|
||||
configId: latest.config_id,
|
||||
state: {
|
||||
timestamp: latest.timestamp,
|
||||
monitor: {
|
||||
name: latest.monitor?.name,
|
||||
type: latest.monitor?.type,
|
||||
duration: latest.monitor?.duration,
|
||||
},
|
||||
url: latest.url ?? {},
|
||||
summary: {
|
||||
|
|
|
@ -179,7 +179,15 @@ export class SyntheticsService {
|
|||
};
|
||||
}
|
||||
|
||||
async pushConfigs(request?: KibanaRequest, configs?: SyntheticsMonitorWithId[]) {
|
||||
async pushConfigs(
|
||||
request?: KibanaRequest,
|
||||
configs?: Array<
|
||||
SyntheticsMonitorWithId & {
|
||||
fields_under_root?: boolean;
|
||||
fields?: { config_id: string };
|
||||
}
|
||||
>
|
||||
) {
|
||||
const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs()));
|
||||
if (monitors.length === 0) {
|
||||
this.logger.debug('No monitor found which can be pushed to service.');
|
||||
|
@ -226,6 +234,32 @@ export class SyntheticsService {
|
|||
}
|
||||
}
|
||||
|
||||
async triggerConfigs(
|
||||
request?: KibanaRequest,
|
||||
configs?: Array<
|
||||
SyntheticsMonitorWithId & {
|
||||
fields_under_root?: boolean;
|
||||
fields?: { config_id: string; test_run_id: string };
|
||||
}
|
||||
>
|
||||
) {
|
||||
const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs()));
|
||||
if (monitors.length === 0) {
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
monitors,
|
||||
output: await this.getOutput(request),
|
||||
};
|
||||
|
||||
try {
|
||||
return await this.apiClient.runOnce(data);
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteConfigs(request: KibanaRequest, configs: SyntheticsMonitorWithId[]) {
|
||||
const data = {
|
||||
monitors: this.formatConfigs(configs),
|
||||
|
|
|
@ -37,6 +37,7 @@ import { addSyntheticsMonitorRoute } from './synthetics_service/add_monitor';
|
|||
import { editSyntheticsMonitorRoute } from './synthetics_service/edit_monitor';
|
||||
import { deleteSyntheticsMonitorRoute } from './synthetics_service/delete_monitor';
|
||||
import { runOnceSyntheticsMonitorRoute } from './synthetics_service/run_once_monitor';
|
||||
import { testNowMonitorRoute } from './synthetics_service/test_now_monitor';
|
||||
|
||||
export * from './types';
|
||||
export { createRouteWithAuth } from './create_route_with_auth';
|
||||
|
@ -69,4 +70,5 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [
|
|||
editSyntheticsMonitorRoute,
|
||||
deleteSyntheticsMonitorRoute,
|
||||
runOnceSyntheticsMonitorRoute,
|
||||
testNowMonitorRoute,
|
||||
];
|
||||
|
|
|
@ -38,6 +38,10 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
{
|
||||
...newMonitor.attributes,
|
||||
id: newMonitor.id,
|
||||
fields: {
|
||||
config_id: newMonitor.id,
|
||||
},
|
||||
fields_under_root: true,
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -47,6 +47,10 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({
|
|||
{
|
||||
...(editMonitor.attributes as SyntheticsMonitor),
|
||||
id: editMonitor.id,
|
||||
fields: {
|
||||
config_id: editMonitor.id,
|
||||
},
|
||||
fields_under_root: true,
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { SyntheticsMonitor } from '../../../common/runtime_types';
|
||||
import { UMRestApiRouteFactory } from '../types';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor';
|
||||
|
||||
export const testNowMonitorRoute: UMRestApiRouteFactory = () => ({
|
||||
method: 'GET',
|
||||
path: API_URLS.TRIGGER_MONITOR + '/{monitorId}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
monitorId: schema.string({ minLength: 1, maxLength: 1024 }),
|
||||
}),
|
||||
},
|
||||
handler: async ({ request, savedObjectsClient, response, server }): Promise<any> => {
|
||||
const { monitorId } = request.params;
|
||||
const monitor = await savedObjectsClient.get<SyntheticsMonitor>(
|
||||
syntheticsMonitorType,
|
||||
monitorId
|
||||
);
|
||||
|
||||
const { syntheticsService } = server;
|
||||
|
||||
const testRunId = uuidv4();
|
||||
|
||||
const errors = await syntheticsService.triggerConfigs(request, [
|
||||
{
|
||||
...monitor.attributes,
|
||||
id: monitorId,
|
||||
fields_under_root: true,
|
||||
fields: { config_id: monitorId, test_run_id: testRunId },
|
||||
},
|
||||
]);
|
||||
|
||||
if (errors && errors?.length > 0) {
|
||||
return { errors, testRunId, monitorId };
|
||||
}
|
||||
|
||||
return { testRunId, monitorId };
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue