[Uptime] Remove monitor states graphql (#62853)

* WIP replacing GQL with redux/rest.

* Finish implementing migration.

* Introduce new connected component for ping list.

* Replace GraphQL type with io-ts.

* Update some broken tests.

* Add test for new helper function.

* Write test snapshots.

* Migrate api tests from graphql to rest.

* Update fixtures that rely on pings.

* Move ping types to runtime_types folder with rest of io-ts files.

* Update Ping type location and imports, type checking.

* Remove reliance on fixtures for ping functional API tests.

* Fix broken unit tests.

* Fix broken types.

* Remove local state storage from parent components.

* Add functional test to cover Ping List functionality.

* Fix monitor page functional test that was broken by merge conflicts.

* Fix broken tests.

* Fix broken API test.

* Replace a test with a describe block that will pre-navigate all tests.

* Delete unused reducer keys.

* Re-introduce loading to ping list reducer.

* Inroduce code that will cause PingList to re-fetch when refresh button is pressed.

* Update expanded rows to support multiple concurrent expanded rows.

* Modify pingList reducer to have singular optional error field.

* Delete unnecessary helper code.

* Delete unused interface.

* Add runtime_type to parse getPings params, fix pagination index.

* Add dedicated monitor type to runtime_types.

* Fix broken tests.

* Fix broken tests.

* Rename '@timestamp' property to 'timestamp' on Ping type.

* Fix broken type and key pings list table on document ID instead of timestamp.

* Fix broken unit tests.

* Fix broken tests and types.

* Fix broken functional test.

* Add REST endpoint for monitor states.

* Add REST route to constants file.

* Introduce io-ts typing for monitor states.

* Remove remaining GraphQL types.

* Update monitor states types to use io-ts types.

* Add state management for monitor states.

* Introduce connected monitor list component.

* Fixup runtime types for monitor states.

* Remove all remaining references to apollo graphql.

* Update URL generator function tests to use inline snapshots instead of snapshot files.

* Fix missing imports and small type issues.

* Prefer inline snapshot to object literal comparison.

* Add type check and console log to API response.

* Update README to remove graphql references.

* Fix type error.

* Make monitor list refresh when global refresh button is pressed.

* Fix broken types.

* Rename `@timestamp` field to `timestamp`.

* Change spelling of var.

* Add timestamp map for `@timestamp` field in monitor states fetcher.

* Remove need for `monito_states` fixture.

* Write test code that allows for deletion of the `monitor_states_id_filtered` fixture.

* Rewrite pagination tests to no longer rely on monitor states page fixtures.

* Skip test that is causing other functional tests to fail.

* Remove unused translations.

* Fix broken test snapshots.

* Fix stale error reporting errors.

* Remove runtime validation from REST handler.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Justin Kambic 2020-04-17 11:50:20 -04:00 committed by GitHub
parent c4ddd00540
commit 7efe7e88d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
146 changed files with 2269 additions and 35220 deletions

View file

@ -3,7 +3,7 @@
## Purpose
The purpose of this plugin is to provide users of Heartbeat more visibility of what's happening
in their infrastructure. It's primarily built using React and Apollo's GraphQL tools.
in their infrastructure.
## Layout
@ -11,13 +11,15 @@ There are three sections to the app, `common`, `public`, and `server`.
### common
Contains GraphQL types, constants and a few other files.
Contains runtime types types, constants and a few other files.
Notably, we use `io-ts`/`fp-ts` functions and types to help provide
additional runtime safety for our API requests/responses.
### public
Components come in two main types, queries and functional. Queries are extended from Apollo's queries
type which abstracts a lot of the GraphQL connectivity away. Functional are dumb components that
don't store any state.
We use Redux and associated tools for managing our app state. Components come in the usual `connect`ed and
presentational varieties.
The `lib` directory controls bootstrapping code and adapter types.
@ -27,12 +29,13 @@ The principal structure of the app is stored in `uptime_app.tsx`.
### server
There is a `graphql` directory which contains the resolvers, schema files, and constants.
The `lib` directory contains `adapters`, which are connections to external resources like Kibana
Server, Elasticsearch, etc. In addition, it contains domains, which are libraries that provide
functionality via adapters.
The `requests` directory contains functions responsible for querying Elasticsearch and parsing its
responses.
There's also a `rest_api` folder that defines the structure of the RESTful API endpoints.
## Testing

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SortOrder, CursorDirection } from '../graphql/types';
import { CursorDirection, SortOrder } from '../runtime_types';
/**
* The Uptime UI utilizes a settings context, the defaults for which are stored here.

View file

@ -7,6 +7,7 @@
export enum API_URLS {
INDEX_PATTERN = `/api/uptime/index_pattern`,
INDEX_STATUS = '/api/uptime/index_status',
MONITOR_LIST = `/api/uptime/monitor/list`,
MONITOR_LOCATIONS = `/api/uptime/monitor/locations`,
MONITOR_DURATION = `/api/uptime/monitor/duration`,
MONITOR_DETAILS = `/api/uptime/monitor/details`,

View file

@ -1,232 +0,0 @@
/* tslint:disable */
// ====================================================
// START: Typescript template
// ====================================================
// ====================================================
// Scalars
// ====================================================
export type UnsignedInteger = any;
// ====================================================
// Types
// ====================================================
export interface Query {
/** Fetches the current state of Uptime monitors for the given parameters. */
getMonitorStates?: MonitorSummaryResult | null;
}
/** The monitor's status for a ping */
export interface Duration {
us?: UnsignedInteger | null;
}
export interface Rtt {
connect?: Duration | null;
handshake?: Duration | null;
validate?: Duration | null;
}
export interface Summary {
up?: number | null;
down?: number | null;
geo?: CheckGeo | null;
}
export interface CheckGeo {
name?: string | null;
location?: Location | null;
}
export interface Location {
lat?: number | null;
lon?: number | null;
}
export interface DocCount {
count: UnsignedInteger;
}
/** The primary object returned for monitor states. */
export interface MonitorSummaryResult {
/** Used to go to the next page of results */
prevPagePagination?: string | null;
/** Used to go to the previous page of results */
nextPagePagination?: string | null;
/** The objects representing the state of a series of heartbeat monitors. */
summaries?: MonitorSummary[] | null;
/** The number of summaries. */
totalSummaryCount: number;
}
/** Represents the current state and associated data for an Uptime monitor. */
export interface MonitorSummary {
/** The ID assigned by the config or generated by the user. */
monitor_id: string;
/** The state of the monitor and its associated details. */
state: State;
histogram?: SummaryHistogram | null;
}
/** Unifies the subsequent data for an uptime monitor. */
export interface State {
/** The agent processing the monitor. */
agent?: Agent | null;
/** There is a check object for each instance of the monitoring agent. */
checks?: Check[] | null;
geo?: StateGeo | null;
observer?: StateObserver | null;
monitor?: MonitorState | null;
summary: Summary;
timestamp: UnsignedInteger;
/** Transport encryption information. */
tls?: (StateTls | null)[] | null;
url?: StateUrl | null;
}
export interface Agent {
id: string;
}
export interface Check {
agent?: Agent | null;
container?: StateContainer | null;
kubernetes?: StateKubernetes | null;
monitor: CheckMonitor;
observer?: CheckObserver | null;
timestamp: string;
}
export interface StateContainer {
id?: string | null;
}
export interface StateKubernetes {
pod?: StatePod | null;
}
export interface StatePod {
uid?: string | null;
}
export interface CheckMonitor {
ip?: string | null;
name?: string | null;
status: string;
}
export interface CheckObserver {
geo?: CheckGeo | null;
}
export interface StateGeo {
name?: (string | null)[] | null;
location?: Location | null;
}
export interface StateObserver {
geo?: StateGeo | null;
}
export interface MonitorState {
status?: string | null;
name?: string | null;
id?: string | null;
type?: string | null;
}
/** Contains monitor transmission encryption information. */
export interface StateTls {
/** The date and time after which the certificate is invalid. */
certificate_not_valid_after?: string | null;
certificate_not_valid_before?: string | null;
certificates?: string | null;
rtt?: Rtt | null;
}
export interface StateUrl {
domain?: string | null;
full?: string | null;
path?: string | null;
port?: number | null;
scheme?: string | null;
}
/** Monitor status data over time. */
export interface SummaryHistogram {
/** The number of documents used to assemble the histogram. */
count: number;
/** The individual histogram data points. */
points: SummaryHistogramPoint[];
}
/** Represents a monitor's statuses for a period of time. */
export interface SummaryHistogramPoint {
/** The time at which these data were collected. */
timestamp: UnsignedInteger;
/** The number of _up_ documents. */
up: number;
/** The number of _down_ documents. */
down: number;
}
export interface GetMonitorStatesQueryArgs {
dateRangeStart: string;
dateRangeEnd: string;
pagination?: string | null;
filters?: string | null;
statusFilter?: string | null;
pageSize: number;
}
// ====================================================
// Enums
// ====================================================
export enum CursorDirection {
AFTER = 'AFTER',
BEFORE = 'BEFORE',
}
export enum SortOrder {
ASC = 'ASC',
DESC = 'DESC',
}
// ====================================================
// END: Typescript template
// ====================================================

View file

@ -6,3 +6,4 @@
export * from './details';
export * from './locations';
export * from './state';

View file

@ -0,0 +1,145 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as t from 'io-ts';
export const CheckMonitorType = t.intersection([
t.partial({
name: t.string,
ip: t.union([t.array(t.string), t.string]),
}),
t.type({
status: t.string,
}),
]);
export const CheckType = t.intersection([
t.partial({
agent: t.partial({
id: t.string,
}),
container: t.type({
id: t.string,
}),
kubernetes: t.type({
pod: t.type({
uid: t.string,
}),
}),
observer: t.type({
geo: t.partial({
name: t.string,
location: t.partial({
lat: t.number,
lon: t.number,
}),
}),
}),
}),
t.type({
monitor: CheckMonitorType,
timestamp: t.number,
}),
]);
export type Check = t.TypeOf<typeof CheckType>;
export const StateType = t.intersection([
t.partial({
checks: t.array(CheckType),
observer: t.partial({
geo: t.partial({
name: t.array(t.string),
}),
}),
summary: t.partial({
up: t.number,
down: t.number,
geo: t.partial({
name: t.string,
location: t.partial({
lat: t.number,
lon: t.number,
}),
}),
}),
}),
t.type({
timestamp: t.string,
url: t.partial({
domain: t.string,
full: t.string,
path: t.string,
port: t.number,
scheme: t.string,
}),
}),
]);
export const HistogramPointType = t.type({
timestamp: t.number,
up: t.number,
down: t.number,
});
export type HistogramPoint = t.TypeOf<typeof HistogramPointType>;
export const HistogramType = t.type({
count: t.number,
points: t.array(HistogramPointType),
});
export type Histogram = t.TypeOf<typeof HistogramType>;
export const MonitorSummaryType = t.intersection([
t.type({
monitor_id: t.string,
state: StateType,
}),
t.partial({
histogram: HistogramType,
}),
]);
export type MonitorSummary = t.TypeOf<typeof MonitorSummaryType>;
export const MonitorSummaryResultType = t.intersection([
t.partial({
summaries: t.array(MonitorSummaryType),
}),
t.type({
prevPagePagination: t.union([t.string, t.null]),
nextPagePagination: t.union([t.string, t.null]),
totalSummaryCount: t.number,
}),
]);
export type MonitorSummaryResult = t.TypeOf<typeof MonitorSummaryResultType>;
export const FetchMonitorStatesQueryArgsType = t.intersection([
t.partial({
pagination: t.string,
filters: t.string,
statusFilter: t.string,
}),
t.type({
dateRangeStart: t.string,
dateRangeEnd: t.string,
pageSize: t.number,
}),
]);
export type FetchMonitorStatesQueryArgs = t.TypeOf<typeof FetchMonitorStatesQueryArgsType>;
export enum CursorDirection {
AFTER = 'AFTER',
BEFORE = 'BEFORE',
}
export enum SortOrder {
ASC = 'ASC',
DESC = 'DESC',
}

View file

@ -11,6 +11,7 @@ export { KueryBar } from './kuerybar/kuery_bar_container';
export { FilterGroup } from './filter_group/filter_group_container';
export { MonitorStatusDetails } from './monitor/status_details_container';
export { MonitorStatusBar } from './monitor/status_bar_container';
export { MonitorList } from './monitor/monitor_list';
export { MonitorListDrawer } from './monitor/list_drawer_container';
export { MonitorListActionsPopover } from './monitor/drawer_popover_container';
export { PingList, PingListProps } from './pings';

View file

@ -12,8 +12,7 @@ import { MonitorDetailsActionPayload } from '../../../state/actions/types';
import { getMonitorDetailsAction } from '../../../state/actions/monitor';
import { MonitorListDrawerComponent } from '../../functional/monitor_list/monitor_list_drawer/monitor_list_drawer';
import { useGetUrlParams } from '../../../hooks';
import { MonitorSummary } from '../../../../common/graphql/types';
import { MonitorDetails } from '../../../../common/runtime_types/monitor';
import { MonitorDetails, MonitorSummary } from '../../../../common/runtime_types';
interface ContainerProps {
summary: MonitorSummary;

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { getMonitorList } from '../../../state/actions';
import { FetchMonitorStatesQueryArgs } from '../../../../common/runtime_types';
import { monitorListSelector } from '../../../state/selectors';
import { MonitorListComponent } from '../../functional/monitor_list';
export interface MonitorListProps {
filters?: string;
linkParameters?: string;
}
export const MonitorList: React.FC<MonitorListProps> = props => {
const dispatch = useDispatch();
const dispatchCallback = useCallback(
(params: FetchMonitorStatesQueryArgs) => {
dispatch(getMonitorList(params));
},
[dispatch]
);
const monitorListState = useSelector(monitorListSelector);
return (
<MonitorListComponent {...props} {...monitorListState} getMonitorList={dispatchCallback} />
);
};

View file

@ -82,7 +82,6 @@ exports[`MonitorBarSeries component shallow renders a series when there are down
}
>
<MonitorBarSeries
dangerColor="A danger color"
histogramSeries={
Array [
Object {

View file

@ -7,14 +7,13 @@
import React from 'react';
import { MonitorBarSeries, MonitorBarSeriesProps } from '../monitor_bar_series';
import { renderWithRouter, shallowWithRouter } from '../../../../lib';
import { SummaryHistogramPoint } from '../../../../../common/graphql/types';
import { HistogramPoint } from '../../../../../common/runtime_types';
describe('MonitorBarSeries component', () => {
let props: MonitorBarSeriesProps;
let histogramSeries: SummaryHistogramPoint[];
let histogramSeries: HistogramPoint[];
beforeEach(() => {
props = {
dangerColor: 'A danger color',
histogramSeries: [
{
timestamp: 124,
@ -193,16 +192,12 @@ describe('MonitorBarSeries component', () => {
});
it('shallow renders nothing if the data series is null', () => {
const component = shallowWithRouter(
<MonitorBarSeries dangerColor="danger" histogramSeries={null} />
);
const component = shallowWithRouter(<MonitorBarSeries histogramSeries={null} />);
expect(component).toEqual({});
});
it('renders if the data series is present', () => {
const component = renderWithRouter(
<MonitorBarSeries dangerColor="danger" histogramSeries={histogramSeries} />
);
const component = renderWithRouter(<MonitorBarSeries histogramSeries={histogramSeries} />);
expect(component).toMatchSnapshot();
});
});

View file

@ -14,23 +14,20 @@ import {
timeFormatter,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import React from 'react';
import React, { useContext } from 'react';
import moment from 'moment';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiText, EuiToolTip } from '@elastic/eui';
import { SummaryHistogramPoint } from '../../../../common/graphql/types';
import { HistogramPoint } from '../../../../common/runtime_types';
import { getChartDateLabel, seriesHasDownValues } from '../../../lib/helper';
import { useUrlParams } from '../../../hooks';
import { UptimeThemeContext } from '../../../contexts';
export interface MonitorBarSeriesProps {
/**
* The color to use for the display of down states.
*/
dangerColor: string;
/**
* The timeseries data to display.
*/
histogramSeries: SummaryHistogramPoint[] | null;
histogramSeries: HistogramPoint[] | null;
}
/**
@ -38,7 +35,10 @@ export interface MonitorBarSeriesProps {
* so we will only render the series component if there are down counts for the selected monitor.
* @param props - the values for the monitor this chart visualizes
*/
export const MonitorBarSeries = ({ dangerColor, histogramSeries }: MonitorBarSeriesProps) => {
export const MonitorBarSeries = ({ histogramSeries }: MonitorBarSeriesProps) => {
const {
colors: { danger },
} = useContext(UptimeThemeContext);
const [getUrlParams, updateUrlParams] = useUrlParams();
const { absoluteDateRangeStart, absoluteDateRangeEnd } = getUrlParams();
@ -68,7 +68,7 @@ export const MonitorBarSeries = ({ dangerColor, histogramSeries }: MonitorBarSer
/>
<BarSeries
id={id}
color={dangerColor}
color={danger}
data={(histogramSeries || []).map(({ timestamp, down }) => [timestamp, down])}
name={i18n.translate('xpack.uptime.monitorList.downLineSeries.downLabel', {
defaultMessage: 'Down checks',

View file

@ -13,7 +13,7 @@ export * from './alerts';
export { DonutChart } from './charts/donut_chart';
export { KueryBarComponent } from './kuery_bar/kuery_bar';
export { MonitorCharts } from './monitor_charts';
export { MonitorList } from './monitor_list';
export { MonitorListComponent } from './monitor_list';
export { OverviewPageParsingErrorCallout } from './overview_page_parsing_error_callout';
export { PingListComponent } from './ping_list';
export { PingHistogramComponent } from './charts';

View file

@ -1,125 +1,535 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MonitorList component renders a no items message when no data is provided 1`] = `
<Fragment>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Monitor status"
id="xpack.uptime.monitorList.monitoringStatusTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="s"
/>
<EuiBasicTable
aria-label="Monitor Status table with columns for Status, Name, URL, IP, Downtime History and Integrations. The table is currently displaying 0 items."
columns={
Array [
exports[`MonitorList component MonitorListPagination component renders a no items message when no data is provided 1`] = `
<ContextProvider
value={
Object {
"history": Object {
"action": "POP",
"block": [Function],
"canGo": [Function],
"createHref": [Function],
"entries": Array [
Object {
"align": "left",
"field": "state.monitor.status",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Status",
"render": [Function],
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
Object {
"align": "left",
"field": "state.monitor.name",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Name",
"render": [Function],
"sortable": true,
},
Object {
"align": "left",
"field": "state.url.full",
"name": "Url",
"render": [Function],
},
Object {
"align": "center",
"field": "histogram.points",
"mobileOptions": Object {
"show": false,
},
"name": "Downtime history",
"render": [Function],
},
Object {
"align": "right",
"field": "monitor_id",
"isExpander": true,
"name": "",
"render": [Function],
"sortable": true,
"width": "24px",
},
]
],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"index": 0,
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"match": Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
},
"staticContext": undefined,
}
}
>
<MonitorListComponent
getMonitorList={[MockFunction]}
lastRefresh={123}
monitorList={
Object {
"list": Object {
"nextPagePagination": null,
"prevPagePagination": null,
"summaries": Array [],
"totalSummaryCount": 0,
},
"loading": false,
}
hasActions={true}
isExpandable={true}
itemId="monitor_id"
itemIdToExpandedRowMap={Object {}}
items={Array []}
loading={false}
noItemsMessage="No uptime monitors found"
responsive={true}
tableLayout="fixed"
/>
<EuiSpacer
size="m"
/>
<EuiFlexGroup
justifyContent="spaceBetween"
responsive={false}
>
<EuiFlexItem
grow={false}
>
<MonitorListPageSizeSelect
setSize={[MockFunction]}
size={25}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiFlexGroup
responsive={false}
>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.prevButton"
direction="prev"
pagination=""
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.nextButton"
direction="next"
pagination=""
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</Fragment>
}
/>
</ContextProvider>
`;
exports[`MonitorList component MonitorListPagination component renders the pagination 1`] = `
<ContextProvider
value={
Object {
"history": Object {
"action": "POP",
"block": [Function],
"canGo": [Function],
"createHref": [Function],
"entries": Array [
Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"index": 0,
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"match": Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
},
"staticContext": undefined,
}
}
>
<MonitorListComponent
getMonitorList={[MockFunction]}
lastRefresh={123}
monitorList={
Object {
"list": Object {
"nextPagePagination": "{\\"cursorKey\\":{\\"monitor_id\\":456},\\"cursorDirection\\":\\"AFTER\\",\\"sortOrder\\":\\"ASC\\"}",
"prevPagePagination": "{\\"cursorKey\\":{\\"monitor_id\\":123},\\"cursorDirection\\":\\"BEFORE\\",\\"sortOrder\\":\\"ASC\\"}",
"summaries": Array [
Object {
"monitor_id": "foo",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 124,
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "down",
},
"timestamp": 125,
},
Object {
"monitor": Object {
"ip": "127.0.0.3",
"status": "down",
},
"timestamp": 126,
},
],
"summary": Object {
"down": 2,
"up": 1,
},
"timestamp": "123",
"url": Object {},
},
},
Object {
"monitor_id": "bar",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 125,
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "up",
},
"timestamp": 126,
},
],
"summary": Object {
"down": 0,
"up": 2,
},
"timestamp": "125",
"url": Object {},
},
},
],
"totalSummaryCount": 2,
},
"loading": false,
}
}
/>
</ContextProvider>
`;
exports[`MonitorList component renders a no items message when no data is provided 1`] = `
<ContextProvider
value={
Object {
"history": Object {
"action": "POP",
"block": [Function],
"canGo": [Function],
"createHref": [Function],
"entries": Array [
Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"index": 0,
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"match": Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
},
"staticContext": undefined,
}
}
>
<MonitorListComponent
getMonitorList={[MockFunction]}
lastRefresh={123}
monitorList={
Object {
"list": Object {
"nextPagePagination": null,
"prevPagePagination": null,
"summaries": Array [],
"totalSummaryCount": 0,
},
"loading": true,
}
}
/>
</ContextProvider>
`;
exports[`MonitorList component renders error list 1`] = `
<ContextProvider
value={
Object {
"history": Object {
"action": "POP",
"block": [Function],
"canGo": [Function],
"createHref": [Function],
"entries": Array [
Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"index": 0,
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"match": Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
},
"staticContext": undefined,
}
}
>
<MonitorListComponent
getMonitorList={[MockFunction]}
lastRefresh={123}
monitorList={
Object {
"error": [Error: foo message],
"list": Object {
"nextPagePagination": null,
"prevPagePagination": null,
"summaries": Array [
Object {
"monitor_id": "foo",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 124,
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "down",
},
"timestamp": 125,
},
Object {
"monitor": Object {
"ip": "127.0.0.3",
"status": "down",
},
"timestamp": 126,
},
],
"summary": Object {
"down": 2,
"up": 1,
},
"timestamp": "123",
"url": Object {},
},
},
Object {
"monitor_id": "bar",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 125,
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "up",
},
"timestamp": 126,
},
],
"summary": Object {
"down": 0,
"up": 2,
},
"timestamp": "125",
"url": Object {},
},
},
],
"totalSummaryCount": 2,
},
"loading": false,
}
}
/>
</ContextProvider>
`;
exports[`MonitorList component renders loading state 1`] = `
<ContextProvider
value={
Object {
"history": Object {
"action": "POP",
"block": [Function],
"canGo": [Function],
"createHref": [Function],
"entries": Array [
Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"index": 0,
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"match": Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
},
"staticContext": undefined,
}
}
>
<MonitorListComponent
getMonitorList={[MockFunction]}
lastRefresh={123}
monitorList={
Object {
"list": Object {
"nextPagePagination": null,
"prevPagePagination": null,
"summaries": Array [
Object {
"monitor_id": "foo",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 124,
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "down",
},
"timestamp": 125,
},
Object {
"monitor": Object {
"ip": "127.0.0.3",
"status": "down",
},
"timestamp": 126,
},
],
"summary": Object {
"down": 2,
"up": 1,
},
"timestamp": "123",
"url": Object {},
},
},
Object {
"monitor_id": "bar",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 125,
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "up",
},
"timestamp": 126,
},
],
"summary": Object {
"down": 0,
"up": 2,
},
"timestamp": "125",
"url": Object {},
},
},
],
"totalSummaryCount": 2,
},
"loading": true,
}
}
/>
</ContextProvider>
`;
exports[`MonitorList component renders the monitor list 1`] = `
@ -672,185 +1082,132 @@ exports[`MonitorList component renders the monitor list 1`] = `
`;
exports[`MonitorList component shallow renders the monitor list 1`] = `
<Fragment>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Monitor status"
id="xpack.uptime.monitorList.monitoringStatusTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="s"
/>
<EuiBasicTable
aria-label="Monitor Status table with columns for Status, Name, URL, IP, Downtime History and Integrations. The table is currently displaying 2 items."
columns={
Array [
<ContextProvider
value={
Object {
"history": Object {
"action": "POP",
"block": [Function],
"canGo": [Function],
"createHref": [Function],
"entries": Array [
Object {
"align": "left",
"field": "state.monitor.status",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Status",
"render": [Function],
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
Object {
"align": "left",
"field": "state.monitor.name",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Name",
"render": [Function],
"sortable": true,
},
Object {
"align": "left",
"field": "state.url.full",
"name": "Url",
"render": [Function],
},
Object {
"align": "center",
"field": "histogram.points",
"mobileOptions": Object {
"show": false,
},
"name": "Downtime history",
"render": [Function],
},
Object {
"align": "right",
"field": "monitor_id",
"isExpander": true,
"name": "",
"render": [Function],
"sortable": true,
"width": "24px",
},
]
}
hasActions={true}
isExpandable={true}
itemId="monitor_id"
itemIdToExpandedRowMap={Object {}}
items={
Array [
Object {
"monitor_id": "foo",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
],
"go": [Function],
"goBack": [Function],
"goForward": [Function],
"index": 0,
"length": 1,
"listen": [Function],
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [Function],
"replace": [Function],
},
"location": Object {
"hash": "",
"key": "TestKeyForTesting",
"pathname": "/",
"search": "",
"state": undefined,
},
"match": Object {
"isExact": true,
"params": Object {},
"path": "/",
"url": "/",
},
"staticContext": undefined,
}
}
>
<MonitorListComponent
getMonitorList={[MockFunction]}
lastRefresh={123}
monitorList={
Object {
"list": Object {
"nextPagePagination": null,
"prevPagePagination": null,
"summaries": Array [
Object {
"monitor_id": "foo",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 124,
},
"timestamp": "124",
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "down",
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "down",
},
"timestamp": 125,
},
"timestamp": "125",
},
Object {
"monitor": Object {
"ip": "127.0.0.3",
"status": "down",
Object {
"monitor": Object {
"ip": "127.0.0.3",
"status": "down",
},
"timestamp": 126,
},
"timestamp": "126",
],
"summary": Object {
"down": 2,
"up": 1,
},
],
"summary": Object {
"down": 2,
"up": 1,
"timestamp": "123",
"url": Object {},
},
"timestamp": "123",
},
},
Object {
"monitor_id": "bar",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
Object {
"monitor_id": "bar",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": 125,
},
"timestamp": "125",
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "up",
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "up",
},
"timestamp": 126,
},
"timestamp": "126",
],
"summary": Object {
"down": 0,
"up": 2,
},
],
"summary": Object {
"down": 0,
"up": 2,
"timestamp": "125",
"url": Object {},
},
"timestamp": "125",
},
},
]
],
"totalSummaryCount": 2,
},
"loading": false,
}
loading={false}
noItemsMessage="No uptime monitors found"
responsive={true}
tableLayout="fixed"
/>
<EuiSpacer
size="m"
/>
<EuiFlexGroup
justifyContent="spaceBetween"
responsive={false}
>
<EuiFlexItem
grow={false}
>
<MonitorListPageSizeSelect
setSize={[MockFunction]}
size={25}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiFlexGroup
responsive={false}
>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.prevButton"
direction="prev"
pagination=""
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.nextButton"
direction="next"
pagination=""
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</Fragment>
}
/>
</ContextProvider>
`;

View file

@ -1,307 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MonitorListPagination component renders a no items message when no data is provided 1`] = `
<Fragment>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Monitor status"
id="xpack.uptime.monitorList.monitoringStatusTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="s"
/>
<EuiBasicTable
aria-label="Monitor Status table with columns for Status, Name, URL, IP, Downtime History and Integrations. The table is currently displaying 0 items."
columns={
Array [
Object {
"align": "left",
"field": "state.monitor.status",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Status",
"render": [Function],
},
Object {
"align": "left",
"field": "state.monitor.name",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Name",
"render": [Function],
"sortable": true,
},
Object {
"align": "left",
"field": "state.url.full",
"name": "Url",
"render": [Function],
},
Object {
"align": "center",
"field": "histogram.points",
"mobileOptions": Object {
"show": false,
},
"name": "Downtime history",
"render": [Function],
},
Object {
"align": "right",
"field": "monitor_id",
"isExpander": true,
"name": "",
"render": [Function],
"sortable": true,
"width": "24px",
},
]
}
hasActions={true}
isExpandable={true}
itemId="monitor_id"
itemIdToExpandedRowMap={Object {}}
items={Array []}
loading={false}
noItemsMessage="No uptime monitors found"
responsive={true}
tableLayout="fixed"
/>
<EuiSpacer
size="m"
/>
<EuiFlexGroup
justifyContent="spaceBetween"
responsive={false}
>
<EuiFlexItem
grow={false}
>
<MonitorListPageSizeSelect
setSize={[MockFunction]}
size={25}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiFlexGroup
responsive={false}
>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.prevButton"
direction="prev"
pagination=""
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.nextButton"
direction="next"
pagination=""
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</Fragment>
`;
exports[`MonitorListPagination component renders the monitor list 1`] = `
<Fragment>
<EuiPanel>
<EuiTitle
size="xs"
>
<h5>
<FormattedMessage
defaultMessage="Monitor status"
id="xpack.uptime.monitorList.monitoringStatusTitle"
values={Object {}}
/>
</h5>
</EuiTitle>
<EuiSpacer
size="s"
/>
<EuiBasicTable
aria-label="Monitor Status table with columns for Status, Name, URL, IP, Downtime History and Integrations. The table is currently displaying 2 items."
columns={
Array [
Object {
"align": "left",
"field": "state.monitor.status",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Status",
"render": [Function],
},
Object {
"align": "left",
"field": "state.monitor.name",
"mobileOptions": Object {
"fullWidth": true,
},
"name": "Name",
"render": [Function],
"sortable": true,
},
Object {
"align": "left",
"field": "state.url.full",
"name": "Url",
"render": [Function],
},
Object {
"align": "center",
"field": "histogram.points",
"mobileOptions": Object {
"show": false,
},
"name": "Downtime history",
"render": [Function],
},
Object {
"align": "right",
"field": "monitor_id",
"isExpander": true,
"name": "",
"render": [Function],
"sortable": true,
"width": "24px",
},
]
}
hasActions={true}
isExpandable={true}
itemId="monitor_id"
itemIdToExpandedRowMap={Object {}}
items={
Array [
Object {
"monitor_id": "foo",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": "124",
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "down",
},
"timestamp": "125",
},
Object {
"monitor": Object {
"ip": "127.0.0.3",
"status": "down",
},
"timestamp": "126",
},
],
"summary": Object {
"down": 2,
"up": 1,
},
"timestamp": "123",
},
},
Object {
"monitor_id": "bar",
"state": Object {
"checks": Array [
Object {
"monitor": Object {
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": "125",
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "up",
},
"timestamp": "126",
},
],
"summary": Object {
"down": 0,
"up": 2,
},
"timestamp": "125",
},
},
]
}
loading={false}
noItemsMessage="No uptime monitors found"
responsive={true}
tableLayout="fixed"
/>
<EuiSpacer
size="m"
/>
<EuiFlexGroup
justifyContent="spaceBetween"
responsive={false}
>
<EuiFlexItem
grow={false}
>
<MonitorListPageSizeSelect
setSize={[MockFunction]}
size={25}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiFlexGroup
responsive={false}
>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.prevButton"
direction="prev"
pagination="{\\"cursorKey\\":{\\"monitor_id\\":123},\\"cursorDirection\\":\\"BEFORE\\",\\"sortOrder\\":\\"ASC\\"}"
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.nextButton"
direction="next"
pagination="{\\"cursorKey\\":{\\"monitor_id\\":456},\\"cursorDirection\\":\\"AFTER\\",\\"sortOrder\\":\\"ASC\\"}"
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</Fragment>
`;

View file

@ -4,17 +4,30 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import { MonitorSummaryResult } from '../../../../../common/graphql/types';
import {
MonitorSummaryResult,
CursorDirection,
SortOrder,
} from '../../../../../common/runtime_types';
import { MonitorListComponent } from '../monitor_list';
import { renderWithRouter } from '../../../../lib';
import { renderWithRouter, shallowWithRouter } from '../../../../lib';
describe('MonitorList component', () => {
let result: MonitorSummaryResult;
let localStorageMock: any;
beforeEach(() => {
localStorageMock = {
getItem: jest.fn().mockImplementation(() => '25'),
setItem: jest.fn(),
};
// @ts-ignore replacing a call to localStorage we use for monitor list size
global.localStorage = localStorageMock;
result = {
nextPagePagination: null,
prevPagePagination: null,
summaries: [
{
monitor_id: 'foo',
@ -25,21 +38,21 @@ describe('MonitorList component', () => {
ip: '127.0.0.1',
status: 'up',
},
timestamp: '124',
timestamp: 124,
},
{
monitor: {
ip: '127.0.0.2',
status: 'down',
},
timestamp: '125',
timestamp: 125,
},
{
monitor: {
ip: '127.0.0.3',
status: 'down',
},
timestamp: '126',
timestamp: 126,
},
],
summary: {
@ -47,6 +60,7 @@ describe('MonitorList component', () => {
down: 2,
},
timestamp: '123',
url: {},
},
},
{
@ -58,14 +72,14 @@ describe('MonitorList component', () => {
ip: '127.0.0.1',
status: 'up',
},
timestamp: '125',
timestamp: 125,
},
{
monitor: {
ip: '127.0.0.2',
status: 'up',
},
timestamp: '126',
timestamp: 126,
},
],
summary: {
@ -73,6 +87,7 @@ describe('MonitorList component', () => {
down: 0,
},
timestamp: '125',
url: {},
},
},
],
@ -81,15 +96,11 @@ describe('MonitorList component', () => {
});
it('shallow renders the monitor list', () => {
const component = shallowWithIntl(
const component = shallowWithRouter(
<MonitorListComponent
dangerColor="danger"
data={{ monitorStates: result }}
hasActiveFilters={false}
loading={false}
pageSize={25}
setPageSize={jest.fn()}
successColor="primary"
monitorList={{ list: result, loading: false }}
lastRefresh={123}
getMonitorList={jest.fn()}
/>
);
@ -97,15 +108,19 @@ describe('MonitorList component', () => {
});
it('renders a no items message when no data is provided', () => {
const component = shallowWithIntl(
const component = shallowWithRouter(
<MonitorListComponent
dangerColor="danger"
data={{}}
hasActiveFilters={false}
loading={false}
pageSize={25}
setPageSize={jest.fn()}
successColor="primary"
monitorList={{
list: {
summaries: [],
nextPagePagination: null,
prevPagePagination: null,
totalSummaryCount: 0,
},
loading: true,
}}
lastRefresh={123}
getMonitorList={jest.fn()}
/>
);
expect(component).toMatchSnapshot();
@ -114,16 +129,156 @@ describe('MonitorList component', () => {
it('renders the monitor list', () => {
const component = renderWithRouter(
<MonitorListComponent
dangerColor="danger"
data={{ monitorStates: result }}
hasActiveFilters={false}
loading={false}
pageSize={25}
setPageSize={jest.fn()}
successColor="primary"
monitorList={{ list: result, loading: false }}
lastRefresh={123}
getMonitorList={jest.fn()}
/>
);
expect(component).toMatchSnapshot();
});
it('renders error list', () => {
const component = shallowWithRouter(
<MonitorListComponent
monitorList={{ list: result, error: new Error('foo message'), loading: false }}
lastRefresh={123}
getMonitorList={jest.fn()}
/>
);
expect(component).toMatchSnapshot();
});
it('renders loading state', () => {
const component = shallowWithRouter(
<MonitorListComponent
monitorList={{ list: result, loading: true }}
lastRefresh={123}
getMonitorList={jest.fn()}
/>
);
expect(component).toMatchSnapshot();
});
describe('MonitorListPagination component', () => {
let paginationResult: MonitorSummaryResult;
beforeEach(() => {
paginationResult = {
prevPagePagination: JSON.stringify({
cursorKey: { monitor_id: 123 },
cursorDirection: CursorDirection.BEFORE,
sortOrder: SortOrder.ASC,
}),
nextPagePagination: JSON.stringify({
cursorKey: { monitor_id: 456 },
cursorDirection: CursorDirection.AFTER,
sortOrder: SortOrder.ASC,
}),
summaries: [
{
monitor_id: 'foo',
state: {
checks: [
{
monitor: {
ip: '127.0.0.1',
status: 'up',
},
timestamp: 124,
},
{
monitor: {
ip: '127.0.0.2',
status: 'down',
},
timestamp: 125,
},
{
monitor: {
ip: '127.0.0.3',
status: 'down',
},
timestamp: 126,
},
],
summary: {
up: 1,
down: 2,
},
timestamp: '123',
url: {},
},
},
{
monitor_id: 'bar',
state: {
checks: [
{
monitor: {
ip: '127.0.0.1',
status: 'up',
},
timestamp: 125,
},
{
monitor: {
ip: '127.0.0.2',
status: 'up',
},
timestamp: 126,
},
],
summary: {
up: 2,
down: 0,
},
timestamp: '125',
url: {},
},
},
],
totalSummaryCount: 2,
};
});
it('renders the pagination', () => {
const component = shallowWithRouter(
<MonitorListComponent
monitorList={{
list: {
...paginationResult,
},
loading: false,
}}
lastRefresh={123}
getMonitorList={jest.fn()}
/>
);
expect(component).toMatchSnapshot();
});
it('renders a no items message when no data is provided', () => {
const component = shallowWithRouter(
<MonitorListComponent
monitorList={{
list: {
summaries: [],
nextPagePagination: null,
prevPagePagination: null,
totalSummaryCount: 0,
},
loading: false,
}}
lastRefresh={123}
getMonitorList={jest.fn()}
/>
);
expect(component).toMatchSnapshot();
});
});
});

View file

@ -1,126 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import {
CursorDirection,
MonitorSummaryResult,
SortOrder,
} from '../../../../../common/graphql/types';
import { MonitorListComponent } from '../monitor_list';
describe('MonitorListPagination component', () => {
let result: MonitorSummaryResult;
beforeEach(() => {
result = {
prevPagePagination: JSON.stringify({
cursorKey: { monitor_id: 123 },
cursorDirection: CursorDirection.BEFORE,
sortOrder: SortOrder.ASC,
}),
nextPagePagination: JSON.stringify({
cursorKey: { monitor_id: 456 },
cursorDirection: CursorDirection.AFTER,
sortOrder: SortOrder.ASC,
}),
summaries: [
{
monitor_id: 'foo',
state: {
checks: [
{
monitor: {
ip: '127.0.0.1',
status: 'up',
},
timestamp: '124',
},
{
monitor: {
ip: '127.0.0.2',
status: 'down',
},
timestamp: '125',
},
{
monitor: {
ip: '127.0.0.3',
status: 'down',
},
timestamp: '126',
},
],
summary: {
up: 1,
down: 2,
},
timestamp: '123',
},
},
{
monitor_id: 'bar',
state: {
checks: [
{
monitor: {
ip: '127.0.0.1',
status: 'up',
},
timestamp: '125',
},
{
monitor: {
ip: '127.0.0.2',
status: 'up',
},
timestamp: '126',
},
],
summary: {
up: 2,
down: 0,
},
timestamp: '125',
},
},
],
totalSummaryCount: 2,
};
});
it('renders the monitor list', () => {
const component = shallowWithIntl(
<MonitorListComponent
dangerColor="danger"
data={{ monitorStates: result }}
loading={false}
pageSize={25}
setPageSize={jest.fn()}
successColor="primary"
hasActiveFilters={false}
/>
);
expect(component).toMatchSnapshot();
});
it('renders a no items message when no data is provided', () => {
const component = shallowWithIntl(
<MonitorListComponent
dangerColor="danger"
data={{}}
loading={false}
successColor="primary"
pageSize={25}
setPageSize={jest.fn()}
hasActiveFilters={false}
/>
);
expect(component).toMatchSnapshot();
});
});

View file

@ -8,7 +8,7 @@ import React from 'react';
import moment from 'moment';
import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { getLocationStatus, MonitorListStatusColumn } from '../monitor_list_status_column';
import { Check } from '../../../../../common/graphql/types';
import { Check } from '../../../../../common/runtime_types';
import { STATUS } from '../../../../../common/constants';
describe('MonitorListStatusColumn', () => {
@ -29,9 +29,6 @@ describe('MonitorListStatusColumn', () => {
beforeEach(() => {
upChecks = [
{
agent: { id: '6a2f2a1c-e346-49ed-8418-6d48af8841d6' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -46,12 +43,9 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794631464',
timestamp: 1579794631464,
},
{
agent: { id: '1117fd01-bc1a-4aa5-bfab-40ab455eadf9' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -66,12 +60,9 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794634220',
timestamp: 1579794634220,
},
{
agent: { id: 'eda59510-45e8-4dfe-b0f8-959c449e3565' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -86,15 +77,12 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794628368',
timestamp: 1579794628368,
},
];
downChecks = [
{
agent: { id: '6a2f2a1c-e346-49ed-8418-6d48af8841d6' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -109,12 +97,9 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794631464',
timestamp: 1579794631464,
},
{
agent: { id: '1117fd01-bc1a-4aa5-bfab-40ab455eadf9' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -129,12 +114,9 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794634220',
timestamp: 1579794634220,
},
{
agent: { id: 'eda59510-45e8-4dfe-b0f8-959c449e3565' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -149,15 +131,12 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794628368',
timestamp: 1579794628368,
},
];
checks = [
{
agent: { id: '6a2f2a1c-e346-49ed-8418-6d48af8841d6' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -172,12 +151,9 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794631464',
timestamp: 1579794631464,
},
{
agent: { id: '1117fd01-bc1a-4aa5-bfab-40ab455eadf9' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -192,12 +168,9 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794634220',
timestamp: 1579794634220,
},
{
agent: { id: 'eda59510-45e8-4dfe-b0f8-959c449e3565' },
container: null,
kubernetes: null,
monitor: {
ip: '104.86.46.103',
name: '',
@ -212,7 +185,7 @@ describe('MonitorListStatusColumn', () => {
},
},
},
timestamp: '1579794628368',
timestamp: 1579794628368,
},
];
});

View file

@ -4,6 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { MonitorList } from './monitor_list';
export { MonitorListComponent } from './monitor_list';
export { Criteria, Pagination } from './types';
export { LocationLink } from './monitor_list_drawer';

View file

@ -16,17 +16,11 @@ import {
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { withUptimeGraphQL, UptimeGraphQLQueryProps } from '../../higher_order';
import { monitorStatesQuery } from '../../../queries/monitor_states_query';
import {
MonitorSummary,
MonitorSummaryResult,
SummaryHistogramPoint,
} from '../../../../common/graphql/types';
import { HistogramPoint, FetchMonitorStatesQueryArgs } from '../../../../common/runtime_types';
import { MonitorSummary } from '../../../../common/runtime_types';
import { MonitorListStatusColumn } from './monitor_list_status_column';
import { formatUptimeGraphQLErrorList } from '../../../lib/helper/format_error_list';
import { ExpandedRowMap } from './types';
import { MonitorBarSeries } from '../charts';
import { MonitorPageLink } from './monitor_page_link';
@ -34,36 +28,69 @@ import { OverviewPageLink } from './overview_page_link';
import * as labels from './translations';
import { MonitorListDrawer } from '../../connected';
import { MonitorListPageSizeSelect } from './monitor_list_page_size_select';
import { MonitorListProps } from '../../connected/monitor/monitor_list';
import { MonitorList } from '../../../state/reducers/monitor_list';
import { useUrlParams } from '../../../hooks';
interface MonitorListQueryResult {
monitorStates?: MonitorSummaryResult;
interface Props extends MonitorListProps {
lastRefresh: number;
monitorList: MonitorList;
getMonitorList: (params: FetchMonitorStatesQueryArgs) => void;
}
interface MonitorListProps {
dangerColor: string;
hasActiveFilters: boolean;
successColor: string;
linkParameters?: string;
pageSize: number;
setPageSize: (size: number) => void;
}
type Props = UptimeGraphQLQueryProps<MonitorListQueryResult> & MonitorListProps;
const TruncatedEuiLink = styled(EuiLink)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
`;
export const MonitorListComponent = (props: Props) => {
const { dangerColor, data, errors, hasActiveFilters, linkParameters, loading } = props;
const DEFAULT_PAGE_SIZE = 10;
const LOCAL_STORAGE_KEY = 'xpack.uptime.monitorList.pageSize';
const getPageSizeValue = () => {
const value = parseInt(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '', 10);
if (isNaN(value)) {
return DEFAULT_PAGE_SIZE;
}
return value;
};
export const MonitorListComponent: React.FC<Props> = ({
filters,
getMonitorList,
lastRefresh,
monitorList: { list, error, loading },
linkParameters,
}) => {
const [pageSize, setPageSize] = useState<number>(getPageSizeValue());
const [drawerIds, updateDrawerIds] = useState<string[]>([]);
const items = data?.monitorStates?.summaries ?? [];
const [getUrlValues] = useUrlParams();
const { dateRangeStart, dateRangeEnd, pagination, statusFilter } = getUrlValues();
const nextPagePagination = data?.monitorStates?.nextPagePagination ?? '';
const prevPagePagination = data?.monitorStates?.prevPagePagination ?? '';
useEffect(() => {
getMonitorList({
dateRangeStart,
dateRangeEnd,
filters,
pageSize,
pagination,
statusFilter,
});
}, [
getMonitorList,
dateRangeStart,
dateRangeEnd,
filters,
lastRefresh,
pageSize,
pagination,
statusFilter,
]);
const items = list.summaries ?? [];
const nextPagePagination = list.nextPagePagination ?? '';
const prevPagePagination = list.prevPagePagination ?? '';
const getExpandedRowMap = () => {
return drawerIds.reduce((map: ExpandedRowMap, id: string) => {
@ -123,8 +150,8 @@ export const MonitorListComponent = (props: Props) => {
mobileOptions: {
show: false,
},
render: (histogramSeries: SummaryHistogramPoint[] | null) => (
<MonitorBarSeries dangerColor={dangerColor} histogramSeries={histogramSeries} />
render: (histogramSeries: HistogramPoint[] | null) => (
<MonitorBarSeries histogramSeries={histogramSeries} />
),
},
{
@ -153,70 +180,61 @@ export const MonitorListComponent = (props: Props) => {
];
return (
<>
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.monitorList.monitoringStatusTitle"
defaultMessage="Monitor status"
/>
</h5>
</EuiTitle>
<EuiSpacer size="s" />
<EuiBasicTable
aria-label={labels.getDescriptionLabel(items.length)}
error={errors ? formatUptimeGraphQLErrorList(errors) : errors}
// Only set loading to true when there are no items present to prevent the bug outlined in
// in https://github.com/elastic/eui/issues/2393 . Once that is fixed we can simply set the value here to
// loading={loading}
loading={loading && (!items || items.length < 1)}
isExpandable={true}
hasActions={true}
itemId="monitor_id"
itemIdToExpandedRowMap={getExpandedRowMap()}
items={items}
// TODO: not needed without sorting and pagination
// onChange={onChange}
noItemsMessage={
hasActiveFilters ? labels.NO_MONITOR_ITEM_SELECTED : labels.NO_DATA_MESSAGE
}
// TODO: reintegrate pagination in future release
// pagination={pagination}
// TODO: reintegrate sorting in future release
// sorting={sorting}
columns={columns}
/>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="spaceBetween" responsive={false}>
<EuiFlexItem grow={false}>
<MonitorListPageSizeSelect size={props.pageSize} setSize={props.setPageSize} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.prevButton"
direction="prev"
pagination={prevPagePagination}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.nextButton"
direction="next"
pagination={nextPagePagination}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</>
<EuiPanel>
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.uptime.monitorList.monitoringStatusTitle"
defaultMessage="Monitor status"
/>
</h5>
</EuiTitle>
<EuiSpacer size="s" />
<EuiBasicTable
aria-label={labels.getDescriptionLabel(items.length)}
error={error?.message}
// Only set loading to true when there are no items present to prevent the bug outlined in
// in https://github.com/elastic/eui/issues/2393 . Once that is fixed we can simply set the value here to
// loading={loading}
loading={loading && (!items || items.length < 1)}
isExpandable={true}
hasActions={true}
itemId="monitor_id"
itemIdToExpandedRowMap={getExpandedRowMap()}
items={items}
// TODO: not needed without sorting and pagination
// onChange={onChange}
noItemsMessage={!!filters ? labels.NO_MONITOR_ITEM_SELECTED : labels.NO_DATA_MESSAGE}
// TODO: reintegrate pagination in future release
// pagination={pagination}
// TODO: reintegrate sorting in future release
// sorting={sorting}
columns={columns}
/>
<EuiSpacer size="m" />
<EuiFlexGroup justifyContent="spaceBetween" responsive={false}>
<EuiFlexItem grow={false}>
<MonitorListPageSizeSelect size={pageSize} setSize={setPageSize} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.prevButton"
direction="prev"
pagination={prevPagePagination}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<OverviewPageLink
dataTestSubj="xpack.uptime.monitorList.nextButton"
direction="next"
pagination={nextPagePagination}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};
export const MonitorList = withUptimeGraphQL<MonitorListQueryResult, MonitorListProps>(
MonitorListComponent,
monitorStatesQuery
);

View file

@ -71,21 +71,21 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there are
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": "121",
"timestamp": 121,
},
Object {
"monitor": Object {
"ip": "127.0.0.2",
"status": "down",
},
"timestamp": "123",
"timestamp": 123,
},
Object {
"monitor": Object {
"ip": "127.0.0.3",
"status": "up",
},
"timestamp": "125",
"timestamp": 125,
},
],
"summary": Object {
@ -175,7 +175,7 @@ exports[`MonitorListDrawer component renders a MonitorListDrawer when there is o
"ip": "127.0.0.1",
"status": "up",
},
"timestamp": "121",
"timestamp": 121,
},
],
"summary": Object {

View file

@ -5,7 +5,7 @@
*/
import React from 'react';
import { MonitorSummary } from '../../../../../../common/graphql/types';
import { MonitorSummary } from '../../../../../../common/runtime_types';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import { IntegrationGroup } from '../integration_group';
@ -19,6 +19,7 @@ describe('IntegrationGroup', () => {
summary: {},
checks: [],
timestamp: '123',
url: {},
},
};
});

View file

@ -4,10 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import 'jest';
import { MonitorSummary, Check } from '../../../../../../common/graphql/types';
import React from 'react';
import { MonitorListDrawerComponent } from '../monitor_list_drawer';
import { MonitorDetails } from '../../../../../../common/runtime_types';
import { Check, MonitorDetails, MonitorSummary } from '../../../../../../common/runtime_types';
import { shallowWithRouter } from '../../../../../lib';
describe('MonitorListDrawer component', () => {
@ -24,7 +23,7 @@ describe('MonitorListDrawer component', () => {
ip: '127.0.0.1',
status: 'up',
},
timestamp: '121',
timestamp: 121,
},
],
summary: {
@ -77,21 +76,21 @@ describe('MonitorListDrawer component', () => {
ip: '127.0.0.1',
status: 'up',
},
timestamp: '121',
timestamp: 121,
},
{
monitor: {
ip: '127.0.0.2',
status: 'down',
},
timestamp: '123',
timestamp: 123,
},
{
monitor: {
ip: '127.0.0.3',
status: 'up',
},
timestamp: '125',
timestamp: 125,
},
];
summary.state.checks = checks;

View file

@ -8,7 +8,7 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers';
import React from 'react';
import moment from 'moment';
import { MonitorStatusList } from '../monitor_status_list';
import { Check } from '../../../../../../common/graphql/types';
import { Check } from '../../../../../../common/runtime_types';
describe('MonitorStatusList component', () => {
let checks: Check[];
@ -21,110 +21,92 @@ describe('MonitorStatusList component', () => {
beforeEach(() => {
checks = [
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
monitor: {
ip: '151.101.130.217',
name: 'elastic',
status: 'up',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
monitor: {
ip: '151.101.194.217',
name: 'elastic',
status: 'up',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
monitor: {
ip: '151.101.2.217',
name: 'elastic',
status: 'up',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
container: null,
kubernetes: null,
monitor: {
ip: '151.101.66.217',
name: 'elastic',
status: 'up',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
container: null,
kubernetes: null,
monitor: {
ip: '2a04:4e42:200::729',
name: 'elastic',
status: 'down',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
container: null,
kubernetes: null,
monitor: {
ip: '2a04:4e42:400::729',
name: 'elastic',
status: 'down',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
container: null,
kubernetes: null,
monitor: {
ip: '2a04:4e42:600::729',
name: 'elastic',
status: 'down',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
{
agent: { id: '8f9a37fb-573a-4fdc-9895-440a5b39c250' },
container: null,
kubernetes: null,
monitor: {
ip: '2a04:4e42::729',
name: 'elastic',
status: 'down',
},
observer: {
geo: { name: null, location: null },
geo: {},
},
timestamp: '1570538236414',
timestamp: 1570538236414,
},
];
});

View file

@ -19,7 +19,7 @@ import {
getLoggingIpHref,
getLoggingKubernetesHref,
} from '../../../../lib/helper';
import { MonitorSummary } from '../../../../../common/graphql/types';
import { MonitorSummary } from '../../../../../common/runtime_types';
import { UptimeSettingsContext } from '../../../../contexts';
interface IntegrationGroupProps {

View file

@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiPopover, EuiButton } from '@elastic/eui';
import { IntegrationGroup } from './integration_group';
import { MonitorSummary } from '../../../../../common/graphql/types';
import { MonitorSummary } from '../../../../../common/runtime_types';
import { toggleIntegrationsPopover, PopoverState } from '../../../../state/actions';
interface MonitorListActionsPopoverProps {

View file

@ -7,10 +7,9 @@
import React from 'react';
import styled from 'styled-components';
import { EuiLink, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui';
import { MonitorSummary } from '../../../../../common/graphql/types';
import { MostRecentError } from './most_recent_error';
import { MonitorStatusList } from './monitor_status_list';
import { MonitorDetails } from '../../../../../common/runtime_types';
import { MonitorDetails, MonitorSummary } from '../../../../../common/runtime_types';
import { MonitorListActionsPopover } from '../../../connected';
const ContainerDiv = styled.div`

View file

@ -8,9 +8,9 @@ import React from 'react';
import { get, capitalize } from 'lodash';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { Check } from '../../../../../common/graphql/types';
import { LocationLink } from './location_link';
import { MonitorStatusRow } from './monitor_status_row';
import { Check } from '../../../../../common/runtime_types';
import { STATUS, UNNAMED_LOCATION } from '../../../../../common/constants';
interface MonitorStatusListProps {

View file

@ -11,7 +11,7 @@ import { capitalize } from 'lodash';
import styled from 'styled-components';
import { EuiHealth, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui';
import { parseTimestamp } from './parse_timestamp';
import { Check } from '../../../../common/graphql/types';
import { Check } from '../../../../common/runtime_types';
import {
STATUS,
SHORT_TIMESPAN_LOCALE,

View file

@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { UptimeGraphQLQueryProps, withUptimeGraphQL } from './uptime_graphql_query';
export { ResponsiveWrapperProps, withResponsiveWrapper } from './responsive_wrapper';

View file

@ -1,89 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { OperationVariables } from 'apollo-client';
import { GraphQLError } from 'graphql';
import React, { Fragment, useContext, useEffect, useState } from 'react';
import { withApollo, WithApolloClient } from 'react-apollo';
import { formatUptimeGraphQLErrorList } from '../../lib/helper/format_error_list';
import { UptimeRefreshContext } from '../../contexts';
export interface UptimeGraphQLQueryProps<T> {
loading: boolean;
data?: T;
errors?: GraphQLError[];
}
interface UptimeGraphQLProps {
implementsCustomErrorState?: boolean;
variables: OperationVariables;
}
/**
* This HOC abstracts the task of querying our GraphQL endpoint,
* which eliminates the need for a lot of boilerplate code in the other components.
*
* @type T - the expected result's type
* @type P - any props the wrapped component will require
* @param WrappedComponent - the consuming component
* @param query - the graphQL query
*/
export function withUptimeGraphQL<T, P = {}>(WrappedComponent: any, query: any) {
type Props = UptimeGraphQLProps & WithApolloClient<T> & P;
return withApollo((props: Props) => {
const { lastRefresh } = useContext(UptimeRefreshContext);
const [loading, setLoading] = useState<boolean>(true);
const [data, setData] = useState<T | undefined>(undefined);
const [errors, setErrors] = useState<GraphQLError[] | undefined>(undefined);
let updateState = (
loadingVal: boolean,
dataVal: T | undefined,
errorsVal: GraphQLError[] | undefined
) => {
setLoading(loadingVal);
setData(dataVal);
setErrors(errorsVal);
};
const { client, implementsCustomErrorState, variables } = props;
const fetch = () => {
setLoading(true);
client
.query<T>({ fetchPolicy: 'network-only', query, variables })
.then(
(result: any) => {
updateState(result.loading, result.data, result.errors);
},
(result: any) => {
updateState(false, undefined, result.graphQLErrors);
}
);
};
useEffect(() => {
fetch();
/**
* If the `then` handler in `fetch`'s promise is fired after
* this component has unmounted, it will try to set state on an
* unmounted component, which indicates a memory leak and will trigger
* React warnings.
*
* We counteract this side effect by providing a cleanup function that will
* reassign the update function to do nothing with the returned values.
*/
return () => {
// this component is planned to be deprecated, for the time being
// we will want to preserve this for the reason above.
// eslint-disable-next-line react-hooks/exhaustive-deps
updateState = () => {};
};
}, [variables, lastRefresh]);
if (!implementsCustomErrorState && errors && errors.length > 0) {
return <Fragment>{formatUptimeGraphQLErrorList(errors)}</Fragment>;
}
return <WrappedComponent {...props} loading={loading} data={data} errors={errors} />;
});
}

View file

@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { CreateGraphQLClient } from './framework_adapter_types';
export const createApolloClient: CreateGraphQLClient = (uri: string, xsrfHeader: string) =>
new ApolloClient({
link: new HttpLink({ uri, credentials: 'same-origin', headers: { 'kbn-xsrf': xsrfHeader } }),
cache: new InMemoryCache({ dataIdFromObject: () => undefined }),
});

View file

@ -1,12 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
export type GraphQLClient = ApolloClient<NormalizedCacheObject>;
export type CreateGraphQLClient = (url: string, xsrfHeader: string) => GraphQLClient;

View file

@ -20,7 +20,6 @@ import {
DEFAULT_TIMEPICKER_QUICK_RANGES,
} from '../../../../common/constants';
import { UMFrameworkAdapter } from '../../lib';
import { createApolloClient } from './apollo_client_adapter';
export const getKibanaFrameworkAdapter = (
core: CoreStart,
@ -60,7 +59,6 @@ export const getKibanaFrameworkAdapter = (
const props: UptimeAppProps = {
basePath: basePath.get(),
canSave,
client: createApolloClient(`${basePath.get()}/api/uptime/graphql`, 'true'),
core,
darkMode: core.uiSettings.get(DEFAULT_DARK_MODE),
commonlyUsedRanges: core.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES),

View file

@ -1,7 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`formatErrorString returns a formatted string containing each error 1`] = `
"Error: foo is bar
Error: bar is not foo
"
`;

View file

@ -1,41 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { formatUptimeGraphQLErrorList } from '../format_error_list';
describe('formatErrorString', () => {
it('returns an empty string for empty array', () => {
const result = formatUptimeGraphQLErrorList([]);
expect(result).toEqual('');
});
it('returns a formatted string containing each error', () => {
const result = formatUptimeGraphQLErrorList([
{
message: 'foo is bar',
locations: undefined,
path: undefined,
nodes: undefined,
source: undefined,
positions: undefined,
originalError: undefined,
extensions: undefined,
name: 'test error',
},
{
message: 'bar is not foo',
locations: undefined,
path: undefined,
nodes: undefined,
source: undefined,
positions: undefined,
originalError: undefined,
extensions: undefined,
name: 'test error',
},
]);
expect(result).toMatchSnapshot();
});
});

View file

@ -1,20 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import { GraphQLError } from 'graphql';
export const formatUptimeGraphQLErrorList = (errors: GraphQLError[]) =>
errors.reduce(
(errorString, error) =>
errorString.concat(
`${i18n.translate('xpack.uptime.errorMessage', {
values: { message: error.message },
defaultMessage: 'Error: {message}',
})}\n`
),
''
);

View file

@ -1,5 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getApmHref creates href with base path when present 1`] = `"foo/app/apm#/services?kuery=url.domain:%20%22www.elastic.co%22&rangeFrom=now-15m&rangeTo=now"`;
exports[`getApmHref does not add a base path or extra slash when base path is empty string 1`] = `"/app/apm#/services?kuery=url.domain:%20%22www.elastic.co%22&rangeFrom=now-15m&rangeTo=now"`;

View file

@ -1,19 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getInfraHref getInfraContainerHref creates a link for valid parameters 1`] = `"foo/app/metrics/link-to/container-detail/test-container-id"`;
exports[`getInfraHref getInfraContainerHref does not specify a base path when none is available 1`] = `"/app/metrics/link-to/container-detail/test-container-id"`;
exports[`getInfraHref getInfraContainerHref returns the first item when multiple container ids are supplied 1`] = `"bar/app/metrics/link-to/container-detail/test-container-id"`;
exports[`getInfraHref getInfraIpHref creates a link for valid parameters 1`] = `"bar/app/metrics/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`;
exports[`getInfraHref getInfraIpHref does not specify a base path when none is available 1`] = `"/app/metrics/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`;
exports[`getInfraHref getInfraIpHref returns a url for ors between multiple ips 1`] = `"foo/app/metrics/inventory?waffleFilter=(expression:'host.ip%20%3A%20152.151.23.192%20or%20host.ip%20%3A%20151.101.202.217',kind:kuery)"`;
exports[`getInfraHref getInfraKubernetesHref creates a link for valid parameters 1`] = `"foo/app/metrics/link-to/pod-detail/test-pod-uid"`;
exports[`getInfraHref getInfraKubernetesHref does not specify a base path when none is available 1`] = `"/app/metrics/link-to/pod-detail/test-pod-uid"`;
exports[`getInfraHref getInfraKubernetesHref selects the first pod uid when there are multiple 1`] = `"/app/metrics/link-to/pod-detail/test-pod-uid"`;

View file

@ -1,13 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getLoggingHref creates a container href with base path when present 1`] = `"bar/app/logs?logFilter=(expression:'container.id%20:%20test-container-id',kind:kuery)"`;
exports[`getLoggingHref creates a container href without a base path if it's an empty string 1`] = `"/app/logs?logFilter=(expression:'container.id%20:%20test-container-id',kind:kuery)"`;
exports[`getLoggingHref creates a pod href with base path when present 1`] = `"bar/app/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`;
exports[`getLoggingHref creates a pod href without a base path when it's an empty string 1`] = `"/app/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`;
exports[`getLoggingHref creates an ip href with base path when present 1`] = `"bar/app/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`;
exports[`getLoggingHref creates an ip href without a base path when it's an empty string 1`] = `"/app/logs?logFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`;

View file

@ -5,7 +5,7 @@
*/
import { getApmHref } from '../get_apm_href';
import { MonitorSummary } from '../../../../../common/graphql/types';
import { MonitorSummary } from '../../../../../common/runtime_types';
describe('getApmHref', () => {
let summary: MonitorSummary;
@ -29,7 +29,7 @@ describe('getApmHref', () => {
uid: 'test-pod-id',
},
},
timestamp: '123',
timestamp: 123,
},
],
timestamp: '123',
@ -43,11 +43,15 @@ describe('getApmHref', () => {
it('creates href with base path when present', () => {
const result = getApmHref(summary, 'foo', 'now-15m', 'now');
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"foo/app/apm#/services?kuery=url.domain:%20%22www.elastic.co%22&rangeFrom=now-15m&rangeTo=now"`
);
});
it('does not add a base path or extra slash when base path is empty string', () => {
const result = getApmHref(summary, '', 'now-15m', 'now');
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"/app/apm#/services?kuery=url.domain:%20%22www.elastic.co%22&rangeFrom=now-15m&rangeTo=now"`
);
});
});

View file

@ -5,7 +5,7 @@
*/
import { getInfraContainerHref, getInfraKubernetesHref, getInfraIpHref } from '../get_infra_href';
import { MonitorSummary } from '../../../../../common/graphql/types';
import { MonitorSummary } from '../../../../../common/runtime_types';
describe('getInfraHref', () => {
let summary: MonitorSummary;
@ -13,7 +13,6 @@ describe('getInfraHref', () => {
summary = {
monitor_id: 'foo',
state: {
summary: {},
checks: [
{
monitor: {
@ -28,9 +27,11 @@ describe('getInfraHref', () => {
uid: 'test-pod-uid',
},
},
timestamp: '123',
timestamp: 123,
},
],
summary: {},
url: {},
timestamp: '123',
},
};
@ -38,11 +39,15 @@ describe('getInfraHref', () => {
it('getInfraContainerHref creates a link for valid parameters', () => {
const result = getInfraContainerHref(summary, 'foo');
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"foo/app/metrics/link-to/container-detail/test-container-id"`
);
});
it('getInfraContainerHref does not specify a base path when none is available', () => {
expect(getInfraContainerHref(summary, '')).toMatchSnapshot();
expect(getInfraContainerHref(summary, '')).toMatchInlineSnapshot(
`"/app/metrics/link-to/container-detail/test-container-id"`
);
});
it('getInfraContainerHref returns undefined when no container id is present', () => {
@ -65,7 +70,7 @@ describe('getInfraHref', () => {
uid: 'test-pod-uid',
},
},
timestamp: '123',
timestamp: 123,
},
{
monitor: {
@ -80,10 +85,12 @@ describe('getInfraHref', () => {
uid: 'test-pod-uid-bar',
},
},
timestamp: '123',
timestamp: 123,
},
];
expect(getInfraContainerHref(summary, 'bar')).toMatchSnapshot();
expect(getInfraContainerHref(summary, 'bar')).toMatchInlineSnapshot(
`"bar/app/metrics/link-to/container-detail/test-container-id"`
);
});
it('getInfraContainerHref returns undefined when checks are undefined', () => {
@ -94,11 +101,13 @@ describe('getInfraHref', () => {
it('getInfraKubernetesHref creates a link for valid parameters', () => {
const result = getInfraKubernetesHref(summary, 'foo');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(`"foo/app/metrics/link-to/pod-detail/test-pod-uid"`);
});
it('getInfraKubernetesHref does not specify a base path when none is available', () => {
expect(getInfraKubernetesHref(summary, '')).toMatchSnapshot();
expect(getInfraKubernetesHref(summary, '')).toMatchInlineSnapshot(
`"/app/metrics/link-to/pod-detail/test-pod-uid"`
);
});
it('getInfraKubernetesHref returns undefined when no pod data is present', () => {
@ -121,7 +130,7 @@ describe('getInfraHref', () => {
uid: 'test-pod-uid',
},
},
timestamp: '123',
timestamp: 123,
},
{
monitor: {
@ -136,10 +145,12 @@ describe('getInfraHref', () => {
uid: 'test-pod-uid-bar',
},
},
timestamp: '123',
timestamp: 123,
},
];
expect(getInfraKubernetesHref(summary, '')).toMatchSnapshot();
expect(getInfraKubernetesHref(summary, '')).toMatchInlineSnapshot(
`"/app/metrics/link-to/pod-detail/test-pod-uid"`
);
});
it('getInfraKubernetesHref returns undefined when checks are undefined', () => {
@ -148,17 +159,21 @@ describe('getInfraHref', () => {
});
it('getInfraKubernetesHref returns undefined when checks are null', () => {
summary.state.checks![0]!.kubernetes!.pod!.uid = null;
delete summary.state.checks![0]!.kubernetes!.pod!.uid;
expect(getInfraKubernetesHref(summary, '')).toBeUndefined();
});
it('getInfraIpHref creates a link for valid parameters', () => {
const result = getInfraIpHref(summary, 'bar');
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"bar/app/metrics/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`
);
});
it('getInfraIpHref does not specify a base path when none is available', () => {
expect(getInfraIpHref(summary, '')).toMatchSnapshot();
expect(getInfraIpHref(summary, '')).toMatchInlineSnapshot(
`"/app/metrics/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`
);
});
it('getInfraIpHref returns undefined when ip is undefined', () => {
@ -167,14 +182,14 @@ describe('getInfraHref', () => {
});
it('getInfraIpHref returns undefined when ip is null', () => {
summary.state.checks![0].monitor.ip = null;
delete summary.state.checks![0].monitor.ip;
expect(getInfraIpHref(summary, 'foo')).toBeUndefined();
});
it('getInfraIpHref returns a url for ors between multiple ips', () => {
summary.state.checks = [
{
timestamp: '123',
timestamp: 123,
monitor: {
ip: '152.151.23.192',
status: 'up',
@ -193,10 +208,12 @@ describe('getInfraHref', () => {
uid: 'test-pod-uid',
},
},
timestamp: '123',
timestamp: 123,
},
];
expect(getInfraIpHref(summary, 'foo')).toMatchSnapshot();
expect(getInfraIpHref(summary, 'foo')).toMatchInlineSnapshot(
`"foo/app/metrics/inventory?waffleFilter=(expression:'host.ip%20%3A%20152.151.23.192%20or%20host.ip%20%3A%20151.101.202.217',kind:kuery)"`
);
});
it('getInfraIpHref returns undefined if checks are undefined', () => {

View file

@ -9,7 +9,7 @@ import {
getLoggingKubernetesHref,
getLoggingIpHref,
} from '../get_logging_href';
import { MonitorSummary } from '../../../../../common/graphql/types';
import { MonitorSummary } from '../../../../../common/runtime_types';
describe('getLoggingHref', () => {
let summary: MonitorSummary;
@ -33,10 +33,11 @@ describe('getLoggingHref', () => {
uid: 'test-pod-id',
},
},
timestamp: '123',
timestamp: 123,
},
],
timestamp: '123',
url: {},
},
};
});
@ -44,37 +45,49 @@ describe('getLoggingHref', () => {
it('creates a container href with base path when present', () => {
const result = getLoggingContainerHref(summary, 'bar');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"bar/app/logs?logFilter=(expression:'container.id%20:%20test-container-id',kind:kuery)"`
);
});
it(`creates a container href without a base path if it's an empty string`, () => {
const result = getLoggingContainerHref(summary, '');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"/app/logs?logFilter=(expression:'container.id%20:%20test-container-id',kind:kuery)"`
);
});
it(`creates an ip href with base path when present`, () => {
const result = getLoggingKubernetesHref(summary, 'bar');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"bar/app/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`
);
});
it('creates a pod href with base path when present', () => {
const result = getLoggingKubernetesHref(summary, 'bar');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"bar/app/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`
);
});
it(`creates a pod href without a base path when it's an empty string`, () => {
const result = getLoggingKubernetesHref(summary, '');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"/app/logs?logFilter=(expression:'pod.uid%20:%20test-pod-id',kind:kuery)"`
);
});
it(`creates an ip href without a base path when it's an empty string`, () => {
const result = getLoggingIpHref(summary, '');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
expect(result).toMatchInlineSnapshot(
`"/app/logs?logFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`
);
});
it('returns undefined if necessary container is not present', () => {
@ -83,7 +96,7 @@ describe('getLoggingHref', () => {
});
it('returns undefined if necessary container is null', () => {
summary.state.checks![0].container!.id = null;
delete summary.state.checks![0].container!.id;
expect(getLoggingContainerHref(summary, '')).toBeUndefined();
});
@ -93,7 +106,7 @@ describe('getLoggingHref', () => {
});
it('returns undefined if necessary pod is null', () => {
summary.state.checks![0].kubernetes!.pod!.uid = null;
delete summary.state.checks![0].kubernetes!.pod!.uid;
expect(getLoggingKubernetesHref(summary, '')).toBeUndefined();
});
@ -103,7 +116,7 @@ describe('getLoggingHref', () => {
});
it('returns undefined ip href if ip is null', () => {
summary.state.checks![0].monitor.ip = null;
delete summary.state.checks![0].monitor.ip;
expect(getLoggingIpHref(summary, '')).toBeUndefined();
});
});

View file

@ -5,7 +5,7 @@
*/
import { get } from 'lodash';
import { Check } from '../../../../common/graphql/types';
import { Check } from '../../../../common/runtime_types';
/**
* Builds URLs to the designated features by extracting values from the provided

View file

@ -6,7 +6,7 @@
import { get } from 'lodash';
import { addBasePath } from './add_base_path';
import { MonitorSummary } from '../../../../common/graphql/types';
import { MonitorSummary } from '../../../../common/runtime_types';
export const getApmHref = (
summary: MonitorSummary,

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { MonitorSummary } from '../../../../common/graphql/types';
import { MonitorSummary } from '../../../../common/runtime_types';
import { addBasePath } from './add_base_path';
import { buildHref } from './build_href';

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { MonitorSummary } from '../../../../common/graphql/types';
import { MonitorSummary } from '../../../../common/runtime_types';
import { addBasePath } from './add_base_path';
import { buildHref } from './build_href';

View file

@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { SummaryHistogramPoint } from '../../../common/graphql/types';
import { HistogramPoint } from '../../../common/runtime_types';
export const seriesHasDownValues = (series: SummaryHistogramPoint[] | null): boolean => {
export const seriesHasDownValues = (series: HistogramPoint[] | null): boolean => {
return series ? series.some(point => !!point.down) : false;
};

View file

@ -4,9 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import React from 'react';
import { ReactElement } from 'react';
import { ChromeBreadcrumb } from 'src/core/public';
import { UMBadge } from '../badge';
import { UptimeAppProps } from '../uptime_app';
@ -19,9 +17,7 @@ export type UMUpdateBreadcrumbs = (breadcrumbs: ChromeBreadcrumb[]) => void;
export type UMUpdateBadge = (badge: UMBadge) => void;
export type UMGraphQLClient = ApolloClient<NormalizedCacheObject>; // | OtherClientType
export type BootstrapUptimeApp = (props: UptimeAppProps) => React.ReactElement<any>;
export type BootstrapUptimeApp = (props: UptimeAppProps) => ReactElement<any>;
export interface UMFrameworkAdapter {
render(element: any): void;

View file

@ -5,20 +5,15 @@
*/
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import {
MonitorList,
OverviewPageParsingErrorCallout,
StatusPanel,
} from '../components/functional';
import { OverviewPageParsingErrorCallout, StatusPanel } from '../components/functional';
import { useUptimeTelemetry, UptimePage, useGetUrlParams } from '../hooks';
import { stringifyUrlParams } from '../lib/helper/stringify_url_params';
import { useTrackPageview } from '../../../../../plugins/observability/public';
import { DataPublicPluginSetup, IIndexPattern } from '../../../../../../src/plugins/data/public';
import { UptimeThemeContext } from '../contexts';
import { EmptyState, FilterGroup, KueryBar } from '../components/connected';
import { EmptyState, FilterGroup, KueryBar, MonitorList } from '../components/connected';
import { useUpdateKueryString } from '../hooks';
import { PageHeader } from './page_header';
import { useBreadcrumbs } from '../hooks/use_breadcrumbs';
@ -40,34 +35,9 @@ const EuiFlexItemStyled = styled(EuiFlexItem)`
}
`;
// TODO: these values belong deeper down in the monitor
// list pagination control, but are here temporarily until we
// are done removing GraphQL
const DEFAULT_PAGE_SIZE = 10;
const LOCAL_STORAGE_KEY = 'xpack.uptime.monitorList.pageSize';
const getMonitorListPageSizeValue = () => {
const value = parseInt(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '', 10);
if (isNaN(value)) {
return DEFAULT_PAGE_SIZE;
}
return value;
};
export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFilters }: Props) => {
const { colors } = useContext(UptimeThemeContext);
// TODO: this is temporary until we migrate the monitor list to our Redux implementation
const [monitorListPageSize, setMonitorListPageSize] = useState<number>(
getMonitorListPageSizeValue()
);
const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useGetUrlParams();
const {
dateRangeStart,
dateRangeEnd,
pagination,
statusFilter,
search,
filters: urlFilters,
} = params;
const { search, filters: urlFilters } = params;
useUptimeTelemetry(UptimePage.Overview);
@ -80,13 +50,6 @@ export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFi
setEsKueryFilters(esFilters ?? '');
}, [esFilters, setEsKueryFilters]);
const sharedProps = {
dateRangeStart,
dateRangeEnd,
statusFilter,
filters: esFilters,
};
const linkParameters = stringifyUrlParams(params, true);
const heading = i18n.translate('xpack.uptime.overviewPage.headerText', {
@ -117,20 +80,7 @@ export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFi
<EuiSpacer size="s" />
<StatusPanel />
<EuiSpacer size="s" />
<MonitorList
dangerColor={colors.danger}
hasActiveFilters={!!esFilters}
implementsCustomErrorState={true}
linkParameters={linkParameters}
pageSize={monitorListPageSize}
setPageSize={setMonitorListPageSize}
successColor={colors.success}
variables={{
...sharedProps,
pagination,
pageSize: monitorListPageSize,
}}
/>
<MonitorList filters={esFilters} linkParameters={linkParameters} />
</EmptyState>
</>
);

View file

@ -1,110 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import gql from 'graphql-tag';
export const monitorStatesQueryString = `
query MonitorStates($dateRangeStart: String!, $dateRangeEnd: String!, $pagination: String, $filters: String, $statusFilter: String, $pageSize: Int) {
monitorStates: getMonitorStates(
dateRangeStart: $dateRangeStart
dateRangeEnd: $dateRangeEnd
pagination: $pagination
filters: $filters
statusFilter: $statusFilter
pageSize: $pageSize
) {
prevPagePagination
nextPagePagination
totalSummaryCount
summaries {
monitor_id
histogram {
count
points {
timestamp
up
down
}
}
state {
agent {
id
}
checks {
agent {
id
}
container {
id
}
kubernetes {
pod {
uid
}
}
monitor {
ip
name
status
}
observer {
geo {
name
location {
lat
lon
}
}
}
timestamp
}
geo {
name
location {
lat
lon
}
}
observer {
geo {
name
location {
lat
lon
}
}
}
monitor {
id
name
status
type
}
summary {
up
down
geo {
name
location {
lat
lon
}
}
}
url {
full
domain
}
timestamp
}
}
}
}
`;
export const monitorStatesQuery = gql`
${monitorStatesQueryString}
`;

View file

@ -7,6 +7,7 @@
export * from './overview_filters';
export * from './snapshot';
export * from './ui';
export * from './monitor_list';
export * from './monitor_status';
export * from './index_patternts';
export * from './ping';

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createAction } from 'redux-actions';
import { FetchMonitorStatesQueryArgs, MonitorSummaryResult } from '../../../common/runtime_types';
export const getMonitorList = createAction<FetchMonitorStatesQueryArgs>('GET_MONITOR_LIST');
export const getMonitorListSuccess = createAction<MonitorSummaryResult>('GET_MONITOR_LIST_SUCCESS');
export const getMonitorListFailure = createAction<Error>('GET_MONITOR_LIST_FAIL');

View file

@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createAction } from 'redux-actions';
import { QueryParams } from './types';
import { Ping } from '../../../common/runtime_types';

View file

@ -5,6 +5,7 @@
*/
export * from './monitor';
export * from './monitor_list';
export * from './overview_filters';
export * from './snapshot';
export * from './monitor_status';

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { API_URLS } from '../../../common/constants';
import { apiService } from './utils';
import {
FetchMonitorStatesQueryArgs,
MonitorSummaryResult,
MonitorSummaryResultType,
} from '../../../common/runtime_types';
export const fetchMonitorList = async (
params: FetchMonitorStatesQueryArgs
): Promise<MonitorSummaryResult> => {
return await apiService.get(API_URLS.MONITOR_LIST, params, MonitorSummaryResultType);
};

View file

@ -8,6 +8,7 @@ import { fork } from 'redux-saga/effects';
import { fetchMonitorDetailsEffect } from './monitor';
import { fetchOverviewFiltersEffect } from './overview_filters';
import { fetchSnapshotCountEffect } from './snapshot';
import { fetchMonitorListEffect } from './monitor_list';
import { fetchMonitorStatusEffect } from './monitor_status';
import { fetchDynamicSettingsEffect, setDynamicSettingsEffect } from './dynamic_settings';
import { fetchIndexPatternEffect } from './index_pattern';
@ -20,6 +21,7 @@ export function* rootEffect() {
yield fork(fetchMonitorDetailsEffect);
yield fork(fetchSnapshotCountEffect);
yield fork(fetchOverviewFiltersEffect);
yield fork(fetchMonitorListEffect);
yield fork(fetchMonitorStatusEffect);
yield fork(fetchDynamicSettingsEffect);
yield fork(setDynamicSettingsEffect);

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { takeLatest } from 'redux-saga/effects';
import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions';
import { fetchMonitorList } from '../api';
import { fetchEffectFactory } from './fetch_effect';
export function* fetchMonitorListEffect() {
yield takeLatest(
getMonitorList,
fetchEffectFactory(fetchMonitorList, getMonitorListSuccess, getMonitorListFailure)
);
}

View file

@ -10,6 +10,7 @@ import { overviewFiltersReducer } from './overview_filters';
import { snapshotReducer } from './snapshot';
import { uiReducer } from './ui';
import { monitorStatusReducer } from './monitor_status';
import { monitorListReducer } from './monitor_list';
import { dynamicSettingsReducer } from './dynamic_settings';
import { indexPatternReducer } from './index_pattern';
import { pingReducer } from './ping';
@ -23,6 +24,7 @@ export const rootReducer = combineReducers({
overviewFilters: overviewFiltersReducer,
snapshot: snapshotReducer,
ui: uiReducer,
monitorList: monitorListReducer,
monitorStatus: monitorStatusReducer,
dynamicSettings: dynamicSettingsReducer,
indexPattern: indexPatternReducer,

View file

@ -24,9 +24,9 @@ const initialState: MonitorDuration = {
errors: [],
};
type PayLoad = MonitorDurationResult & Error;
type Payload = MonitorDurationResult & Error;
export const monitorDurationReducer = handleActions<MonitorDuration, PayLoad>(
export const monitorDurationReducer = handleActions<MonitorDuration, Payload>(
{
[String(getMonitorDurationAction)]: (state: MonitorDuration) => ({
...state,

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { handleActions, Action } from 'redux-actions';
import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions';
import { MonitorSummaryResult } from '../../../common/runtime_types';
export interface MonitorList {
list: MonitorSummaryResult;
error?: Error;
loading: boolean;
}
export const initialState: MonitorList = {
list: {
nextPagePagination: null,
prevPagePagination: null,
summaries: [],
totalSummaryCount: 0,
},
loading: false,
};
type Payload = MonitorSummaryResult & Error;
export const monitorListReducer = handleActions<MonitorList, Payload>(
{
[String(getMonitorList)]: (state: MonitorList) => ({
...state,
loading: true,
}),
[String(getMonitorListSuccess)]: (
state: MonitorList,
action: Action<MonitorSummaryResult>
) => ({
...state,
loading: false,
error: undefined,
list: { ...action.payload },
}),
[String(getMonitorListFailure)]: (state: MonitorList, action: Action<Error>) => ({
...state,
error: action.payload,
loading: false,
}),
},
initialState
);

View file

@ -71,6 +71,15 @@ describe('state selectors', () => {
loading: false,
errors: [],
},
monitorList: {
list: {
prevPagePagination: null,
nextPagePagination: null,
summaries: [],
totalSummaryCount: 0,
},
loading: false,
},
ml: {
mlJob: {
data: null,

View file

@ -92,3 +92,8 @@ export const selectMonitorStatusAlert = ({ indexPattern, overviewFilters, ui }:
export const indexStatusSelector = ({ indexStatus }: AppState) => {
return indexStatus.indexStatus;
};
export const monitorListSelector = ({ monitorList, ui: { lastRefresh } }: AppState) => ({
monitorList,
lastRefresh,
});

View file

@ -7,13 +7,12 @@
import { EuiPage, EuiErrorBoundary } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect } from 'react';
import { ApolloProvider } from 'react-apollo';
import { Provider as ReduxProvider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { I18nStart, ChromeBreadcrumb, CoreStart } from 'src/core/public';
import { PluginsSetup } from 'ui/new_platform/new_platform';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { UMGraphQLClient, UMUpdateBadge } from './lib/lib';
import { UMUpdateBadge } from './lib/lib';
import {
UptimeRefreshContextProvider,
UptimeSettingsContextProvider,
@ -39,7 +38,6 @@ export interface UptimeAppColors {
export interface UptimeAppProps {
basePath: string;
canSave: boolean;
client: UMGraphQLClient;
core: CoreStart;
darkMode: boolean;
i18n: I18nStart;
@ -59,7 +57,6 @@ const Application = (props: UptimeAppProps) => {
const {
basePath,
canSave,
client,
core,
darkMode,
i18n: i18nCore,
@ -97,25 +94,23 @@ const Application = (props: UptimeAppProps) => {
<ReduxProvider store={store}>
<KibanaContextProvider services={{ ...core, ...plugins }}>
<Router basename={routerBasename}>
<ApolloProvider client={client}>
<UptimeRefreshContextProvider>
<UptimeSettingsContextProvider {...props}>
<UptimeThemeContextProvider darkMode={darkMode}>
<UptimeAlertsContextProvider>
<EuiPage className="app-wrapper-panel " data-test-subj="uptimeApp">
<main>
<UptimeAlertsFlyoutWrapper
alertTypeId="xpack.uptime.alerts.monitorStatus"
canChangeTrigger={false}
/>
<PageRouter autocomplete={plugins.data.autocomplete} />
</main>
</EuiPage>
</UptimeAlertsContextProvider>
</UptimeThemeContextProvider>
</UptimeSettingsContextProvider>
</UptimeRefreshContextProvider>
</ApolloProvider>
<UptimeRefreshContextProvider>
<UptimeSettingsContextProvider {...props}>
<UptimeThemeContextProvider darkMode={darkMode}>
<UptimeAlertsContextProvider>
<EuiPage className="app-wrapper-panel " data-test-subj="uptimeApp">
<main>
<UptimeAlertsFlyoutWrapper
alertTypeId="xpack.uptime.alerts.monitorStatus"
canChangeTrigger={false}
/>
<PageRouter autocomplete={plugins.data.autocomplete} />
</main>
</EuiPage>
</UptimeAlertsContextProvider>
</UptimeThemeContextProvider>
</UptimeSettingsContextProvider>
</UptimeRefreshContextProvider>
</Router>
</KibanaContextProvider>
</ReduxProvider>

View file

@ -1,11 +0,0 @@
{
"flattenTypes": true,
"generatorConfig": {},
"primitives": {
"String": "string",
"Int": "number",
"Float": "number",
"Boolean": "boolean",
"ID": "string"
}
}

View file

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
require('../../../../../src/setup_node_env');
const { resolve } = require('path');
// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved
const { generate } = require('graphql-code-generator');
const CONFIG_PATH = resolve(__dirname, 'gql_gen.json');
const OUTPUT_INTROSPECTION_PATH = resolve('common', 'graphql', 'introspection.json');
const OUTPUT_TYPES_PATH = resolve('common', 'graphql', 'types.ts');
const SCHEMA_PATH = resolve(__dirname, 'graphql_schemas.ts');
async function main() {
await generate(
{
args: [],
config: CONFIG_PATH,
out: OUTPUT_INTROSPECTION_PATH,
overwrite: true,
schema: SCHEMA_PATH,
template: 'graphql-codegen-introspection-template',
},
true
);
await generate(
{
args: [],
config: CONFIG_PATH,
out: OUTPUT_TYPES_PATH,
overwrite: true,
schema: SCHEMA_PATH,
template: 'graphql-codegen-typescript-template',
},
true
);
}
if (require.main === module) {
main();
}

View file

@ -16241,7 +16241,6 @@
"xpack.uptime.emptyStateError.notAuthorized": "アップタイムデータの表示が承認されていません。システム管理者にお問い合わせください。",
"xpack.uptime.emptyStateError.notFoundPage": "ページが見つかりません",
"xpack.uptime.emptyStateError.title": "エラー",
"xpack.uptime.errorMessage": "エラー: {message}",
"xpack.uptime.featureCatalogueDescription": "エンドポイントヘルスチェックとアップタイム監視を行います。",
"xpack.uptime.featureRegistry.uptimeFeatureName": "アップタイム",
"xpack.uptime.filterBar.ariaLabel": "概要ページのインプットフィルター基準",

View file

@ -16246,7 +16246,6 @@
"xpack.uptime.emptyStateError.notAuthorized": "您无权查看 Uptime 数据,请联系系统管理员。",
"xpack.uptime.emptyStateError.notFoundPage": "未找到页面",
"xpack.uptime.emptyStateError.title": "错误",
"xpack.uptime.errorMessage": "错误:{message}",
"xpack.uptime.featureCatalogueDescription": "执行终端节点运行状况检查和运行时间监测。",
"xpack.uptime.featureRegistry.uptimeFeatureName": "运行时间",
"xpack.uptime.filterBar.ariaLabel": "概览页面的输入筛选条件",

View file

@ -1,7 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const DEFAULT_GRAPHQL_PATH = '/api/uptime/graphql';

View file

@ -1,17 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createMonitorStatesResolvers, monitorStatesSchema } from './monitor_states';
import { pingsSchema } from './pings';
import { CreateUMGraphQLResolvers } from './types';
import { unsignedIntegerResolverFunctions, unsignedIntegerSchema } from './unsigned_int_scalar';
export { DEFAULT_GRAPHQL_PATH } from './constants';
export const resolvers: CreateUMGraphQLResolvers[] = [
createMonitorStatesResolvers,
unsignedIntegerResolverFunctions,
];
export const typeDefs: any[] = [pingsSchema, unsignedIntegerSchema, monitorStatesSchema];

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { createMonitorStatesResolvers } from './resolvers';
export { monitorStatesSchema } from './schema.gql';

View file

@ -1,76 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { CreateUMGraphQLResolvers, UMContext } from '../types';
import { UMServerLibs } from '../../lib/lib';
import { UMResolver } from '../../../../../legacy/plugins/uptime/common/graphql/resolver_types';
import {
GetMonitorStatesQueryArgs,
MonitorSummaryResult,
} from '../../../../../legacy/plugins/uptime/common/graphql/types';
import { CONTEXT_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants';
import { savedObjectsAdapter } from '../../lib/saved_objects';
export type UMGetMonitorStatesResolver = UMResolver<
MonitorSummaryResult | Promise<MonitorSummaryResult>,
any,
GetMonitorStatesQueryArgs,
UMContext
>;
export const createMonitorStatesResolvers: CreateUMGraphQLResolvers = (
libs: UMServerLibs
): {
Query: {
getMonitorStates: UMGetMonitorStatesResolver;
};
} => {
return {
Query: {
async getMonitorStates(
_resolver,
{ dateRangeStart, dateRangeEnd, filters, pagination, statusFilter, pageSize },
{ APICaller, savedObjectsClient }
): Promise<MonitorSummaryResult> {
const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(
savedObjectsClient
);
const decodedPagination = pagination
? JSON.parse(decodeURIComponent(pagination))
: CONTEXT_DEFAULTS.CURSOR_PAGINATION;
const [
indexStatus,
{ summaries, nextPagePagination, prevPagePagination },
] = await Promise.all([
libs.requests.getIndexStatus({ callES: APICaller, dynamicSettings }),
libs.requests.getMonitorStates({
callES: APICaller,
dynamicSettings,
dateRangeStart,
dateRangeEnd,
pagination: decodedPagination,
pageSize,
filters,
// this is added to make typescript happy,
// this sort of reassignment used to be further downstream but I've moved it here
// because this code is going to be decomissioned soon
statusFilter: statusFilter || undefined,
}),
]);
const totalSummaryCount = indexStatus?.docCount ?? 0;
return {
summaries,
nextPagePagination,
prevPagePagination,
totalSummaryCount,
};
},
},
};
};

View file

@ -1,183 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import gql from 'graphql-tag';
export const monitorStatesSchema = gql`
"Represents a monitor's statuses for a period of time."
type SummaryHistogramPoint {
"The time at which these data were collected."
timestamp: UnsignedInteger!
"The number of _up_ documents."
up: Int!
"The number of _down_ documents."
down: Int!
}
"Monitor status data over time."
type SummaryHistogram {
"The number of documents used to assemble the histogram."
count: Int!
"The individual histogram data points."
points: [SummaryHistogramPoint!]!
}
type Agent {
id: String!
}
type Check {
agent: Agent
container: StateContainer
kubernetes: StateKubernetes
monitor: CheckMonitor!
observer: CheckObserver
timestamp: String!
}
type StateContainer {
id: String
}
type StateKubernetes {
pod: StatePod
}
type StatePod {
uid: String
}
type CheckMonitor {
ip: String
name: String
status: String!
}
type Location {
lat: Float
lon: Float
}
type CheckGeo {
name: String
location: Location
}
type CheckObserver {
geo: CheckGeo
}
type StateGeo {
name: [String]
location: Location
}
type StateObserver {
geo: StateGeo
}
type MonitorState {
status: String
name: String
id: String
type: String
}
type Summary {
up: Int
down: Int
geo: CheckGeo
}
type MonitorSummaryUrl {
domain: String
fragment: String
full: String
original: String
password: String
path: String
port: Int
query: String
scheme: String
username: String
}
type StateUrl {
domain: String
full: String
path: String
port: Int
scheme: String
}
"Contains monitor transmission encryption information."
type StateTLS {
"The date and time after which the certificate is invalid."
certificate_not_valid_after: String
certificate_not_valid_before: String
certificates: String
rtt: RTT
}
"Unifies the subsequent data for an uptime monitor."
type State {
"The agent processing the monitor."
agent: Agent
"There is a check object for each instance of the monitoring agent."
checks: [Check!]
geo: StateGeo
observer: StateObserver
monitor: MonitorState
summary: Summary!
timestamp: UnsignedInteger!
"Transport encryption information."
tls: [StateTLS]
url: StateUrl
}
"Represents the current state and associated data for an Uptime monitor."
type MonitorSummary {
"The ID assigned by the config or generated by the user."
monitor_id: String!
"The state of the monitor and its associated details."
state: State!
histogram: SummaryHistogram
}
"The primary object returned for monitor states."
type MonitorSummaryResult {
"Used to go to the next page of results"
prevPagePagination: String
"Used to go to the previous page of results"
nextPagePagination: String
"The objects representing the state of a series of heartbeat monitors."
summaries: [MonitorSummary!]
"The number of summaries."
totalSummaryCount: Int!
}
enum CursorDirection {
AFTER
BEFORE
}
enum SortOrder {
ASC
DESC
}
type Query {
"Fetches the current state of Uptime monitors for the given parameters."
getMonitorStates(
dateRangeStart: String!
dateRangeEnd: String!
pagination: String
filters: String
statusFilter: String
pageSize: Int
): MonitorSummaryResult
}
`;

View file

@ -1,7 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { pingsSchema } from './schema.gql';

View file

@ -1,264 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import gql from 'graphql-tag';
export const pingsSchema = gql`
type PingResults {
"Total number of matching pings"
total: UnsignedInteger!
"Unique list of all locations the query matched"
locations: [String!]!
"List of pings "
pings: [Ping!]!
}
type ContainerImage {
name: String
tag: String
}
type Container {
id: String
image: ContainerImage
name: String
runtime: String
}
type DocCount {
count: UnsignedInteger!
}
"The monitor's status for a ping"
type Duration {
us: UnsignedInteger
}
"An agent for recording a beat"
type Beat {
hostname: String
name: String
timezone: String
type: String
}
type Docker {
id: String
image: String
name: String
}
type ECS {
version: String
}
type Error {
code: Int
message: String
type: String
}
type OS {
family: String
kernel: String
platform: String
version: String
name: String
build: String
}
"Geolocation data added via processors to enrich events."
type Geo {
"Name of the city in which the agent is running."
city_name: String
"The name of the continent on which the agent is running."
continent_name: String
"ISO designation for the agent's country."
country_iso_code: String
"The name of the agent's country."
country_name: String
"The lat/long of the agent."
location: String
"A name for the host's location, e.g. 'us-east-1' or 'LAX'."
name: String
"ISO designation of the agent's region."
region_iso_code: String
"Name of the region hosting the agent."
region_name: String
}
type Host {
architecture: String
id: String
hostname: String
ip: String
mac: String
name: String
os: OS
}
type HttpRTT {
content: Duration
response_header: Duration
total: Duration
validate: Duration
validate_body: Duration
write_request: Duration
}
type HTTPBody {
"Size of HTTP response body in bytes"
bytes: UnsignedInteger
"Hash of the HTTP response body"
hash: String
"Response body of the HTTP Response. May be truncated based on client settings."
content: String
"Byte length of the content string, taking into account multibyte chars."
content_bytes: UnsignedInteger
}
type HTTPResponse {
status_code: UnsignedInteger
body: HTTPBody
}
type HTTP {
response: HTTPResponse
rtt: HttpRTT
url: String
}
type ICMP {
requests: Int
rtt: Int
}
type KubernetesContainer {
image: String
name: String
}
type KubernetesNode {
name: String
}
type KubernetesPod {
name: String
uid: String
}
type Kubernetes {
container: KubernetesContainer
namespace: String
node: KubernetesNode
pod: KubernetesPod
}
type MetaCloud {
availability_zone: String
instance_id: String
instance_name: String
machine_type: String
project_id: String
provider: String
region: String
}
type Meta {
cloud: MetaCloud
}
type Monitor {
duration: Duration
host: String
"The id of the monitor"
id: String
"The IP pinged by the monitor"
ip: String
"The name of the protocol being monitored"
name: String
"The protocol scheme of the monitored host"
scheme: String
"The status of the monitored host"
status: String
"The type of host being monitored"
type: String
check_group: String
}
"Metadata added by a proccessor, which is specified in its configuration."
type Observer {
"Geolocation data for the agent."
geo: Geo
}
type Resolve {
host: String
ip: String
rtt: Duration
}
type RTT {
connect: Duration
handshake: Duration
validate: Duration
}
type Socks5 {
rtt: RTT
}
type TCP {
port: Int
rtt: RTT
}
"Contains monitor transmission encryption information."
type PingTLS {
"The date and time after which the certificate is invalid."
certificate_not_valid_after: String
certificate_not_valid_before: String
certificates: String
rtt: RTT
}
type URL {
full: String
scheme: String
domain: String
port: Int
path: String
query: String
}
"A request sent from a monitor to a host"
type Ping {
"unique ID for this ping"
id: String!
"The timestamp of the ping's creation"
timestamp: String!
"The agent that recorded the ping"
beat: Beat
container: Container
docker: Docker
ecs: ECS
error: Error
host: Host
http: HTTP
icmp: ICMP
kubernetes: Kubernetes
meta: Meta
monitor: Monitor
observer: Observer
resolve: Resolve
socks5: Socks5
summary: Summary
tags: String
tcp: TCP
tls: PingTLS
url: URL
}
`;

View file

@ -1,23 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { RequestHandlerContext, CallAPIOptions, SavedObjectsClient } from 'src/core/server';
import { UMServerLibs } from '../lib/lib';
export type UMContext = RequestHandlerContext & {
APICaller: (
endpoint: string,
clientParams?: Record<string, any>,
options?: CallAPIOptions | undefined
) => Promise<any>;
savedObjectsClient: SavedObjectsClient;
};
export interface UMGraphQLResolver {
Query?: any;
}
export type CreateUMGraphQLResolvers = (libs: UMServerLibs) => any;

View file

@ -1,42 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { parseLiteral } from '../resolvers';
describe('parseLiteral', () => {
it('parses string literal of type IntValue', () => {
const result = parseLiteral({
kind: 'IntValue',
value: '1562605032000',
});
expect(result).toBe(1562605032000);
});
it('parses string literal of type FloatValue', () => {
const result = parseLiteral({
kind: 'FloatValue',
value: '1562605032000.0',
});
expect(result).toBe(1562605032000);
});
it('parses string literal of type String', () => {
const result = parseLiteral({
kind: 'StringValue',
value: '1562605032000',
});
expect(result).toBe(1562605032000);
});
it('returns `null` for unsupported types', () => {
expect(
parseLiteral({
kind: 'EnumValue',
value: 'false',
})
).toBeNull();
});
});

View file

@ -1,19 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { parseValue } from '../resolvers';
describe('parseValue', () => {
it(`parses a number value and returns it if its > 0`, () => {
const result = parseValue('1562605032000');
expect(result).toBe(1562605032000);
});
it(`parses a number and returns null if its value is < 0`, () => {
const result = parseValue('-1562605032000');
expect(result).toBeNull();
});
});

View file

@ -1,24 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { serialize } from '../resolvers';
describe('serialize', () => {
it('serializes date strings correctly', () => {
const result = serialize('2019-07-08T16:59:09.796Z');
expect(result).toBe(1562605149796);
});
it('serializes timestamp strings correctly', () => {
const result = serialize('1562605032000');
expect(result).toBe(1562605032000);
});
it('serializes non-date and non-numeric values to NaN', () => {
const result = serialize('foo');
expect(result).toBeNaN();
});
});

View file

@ -1,8 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { unsignedIntegerResolverFunctions } from './resolvers';
export { unsignedIntegerSchema } from './schema.gql';

View file

@ -1,51 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { GraphQLScalarType, Kind, ValueNode } from 'graphql';
import { UMServerLibs } from '../../lib/lib';
import { CreateUMGraphQLResolvers } from '../types';
export const serialize = (value: any): number => {
// `parseInt` will yield `2019` for a value such as "2019-07-08T16:59:09.796Z"
if (isNaN(Number(value))) {
return Date.parse(value);
}
return parseInt(value, 10);
};
export const parseValue = (value: any) => {
const parsed = parseInt(value, 10);
if (parsed < 0) {
return null;
}
return parsed;
};
export const parseLiteral = (ast: ValueNode) => {
switch (ast.kind) {
case Kind.INT:
case Kind.FLOAT:
case Kind.STRING:
return parseInt(ast.value, 10);
}
return null;
};
const unsignedIntegerScalar = new GraphQLScalarType({
name: 'UnsignedInteger',
description: 'Represents an unsigned 32-bit integer',
serialize,
parseValue,
parseLiteral,
});
/**
* This scalar resolver will parse an integer string of > 32 bits and return a value of type `number`.
* This assumes that the code is running in an environment that supports big ints.
*/
export const unsignedIntegerResolverFunctions: CreateUMGraphQLResolvers = (libs: UMServerLibs) => ({
UnsignedInteger: unsignedIntegerScalar,
});

View file

@ -1,11 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import gql from 'graphql-tag';
export const unsignedIntegerSchema = gql`
scalar UnsignedInteger
`;

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { GraphQLSchema } from 'graphql';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import {
IRouter,
@ -44,5 +43,4 @@ export interface UptimeCorePlugins {
export interface UMBackendFrameworkAdapter {
registerRoute(route: UMKibanaRoute): void;
registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void;
}

View file

@ -4,9 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { GraphQLSchema } from 'graphql';
import { schema as kbnSchema } from '@kbn/config-schema';
import { runHttpQuery } from 'apollo-server-core';
import { UptimeCoreSetup } from './adapter_types';
import { UMBackendFrameworkAdapter } from './adapter_types';
import { UMKibanaRoute } from '../../../rest_api';
@ -33,71 +30,4 @@ export class UMKibanaBackendFrameworkAdapter implements UMBackendFrameworkAdapte
throw new Error(`Handler for method ${method} is not defined`);
}
}
public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void {
this.server.route.post(
{
path: routePath,
validate: {
body: kbnSchema.object({
operationName: kbnSchema.nullable(kbnSchema.string()),
query: kbnSchema.string(),
variables: kbnSchema.recordOf(kbnSchema.string(), kbnSchema.any()),
}),
},
options: {
tags: ['access:uptime-read'],
},
},
async (context, request, resp): Promise<any> => {
const {
core: {
elasticsearch: {
dataClient: { callAsCurrentUser },
},
},
} = context;
const options = {
graphQLOptions: (_req: any) => {
return {
context: {
...context,
APICaller: callAsCurrentUser,
savedObjectsClient: context.core.savedObjects.client,
},
schema,
};
},
path: routePath,
route: {
tags: ['access:uptime-read'],
},
};
try {
const query = request.body as Record<string, any>;
const graphQLResponse = await runHttpQuery([request], {
method: 'POST',
options: options.graphQLOptions,
query,
});
return resp.ok({
body: graphQLResponse,
headers: {
'content-type': 'application/json',
},
});
} catch (error) {
if (error.isGraphQLError === true) {
return resp.internalError({
body: { message: error.message },
headers: { 'content-type': 'application/json' },
});
}
return resp.internalError();
}
}
);
}
}

View file

@ -68,11 +68,11 @@ export const getMonitorDetails: UMElasticsearchQueryFn<
const data = result.hits.hits[0]?._source;
const monitorError: MonitorError | undefined = data?.error;
const errorTimeStamp: string | undefined = data?.['@timestamp'];
const errorTimestamp: string | undefined = data?.['@timestamp'];
return {
monitorId,
error: monitorError,
timestamp: errorTimeStamp,
timestamp: errorTimestamp,
};
};

View file

@ -8,10 +8,11 @@ import { CONTEXT_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/co
import { fetchPage } from './search';
import { UMElasticsearchQueryFn } from '../adapters';
import {
MonitorSummary,
SortOrder,
CursorDirection,
} from '../../../../../legacy/plugins/uptime/common/graphql/types';
MonitorSummary,
} from '../../../../../legacy/plugins/uptime/common/runtime_types';
import { QueryContext } from './search';
export interface CursorPagination {

View file

@ -12,7 +12,7 @@ import {
MonitorGroupsPage,
} from '../fetch_page';
import { QueryContext } from '../query_context';
import { MonitorSummary } from '../../../../../../../legacy/plugins/uptime/common/graphql/types';
import { MonitorSummary } from '../../../../../../../legacy/plugins/uptime/common/runtime_types';
import { nextPagination, prevPagination, simpleQueryContext } from './test_helpers';
const simpleFixture: MonitorGroups[] = [
@ -53,12 +53,16 @@ const simpleFetcher = (monitorGroups: MonitorGroups[]): MonitorGroupsFetcher =>
};
const simpleEnricher = (monitorGroups: MonitorGroups[]): MonitorEnricher => {
return async (queryContext: QueryContext, checkGroups: string[]): Promise<MonitorSummary[]> => {
return async (_queryContext: QueryContext, checkGroups: string[]): Promise<MonitorSummary[]> => {
return checkGroups.map(cg => {
const monitorGroup = monitorGroups.find(mg => mg.groups.some(g => g.checkGroup === cg))!;
return {
monitor_id: monitorGroup.id,
state: { summary: {}, timestamp: new Date(Date.parse('1999-12-31')).toISOString() },
state: {
summary: {},
timestamp: new Date(Date.parse('1999-12-31')).valueOf().toString(),
url: {},
},
};
});
};
@ -71,16 +75,37 @@ describe('fetching a page', () => {
simpleFetcher(simpleFixture),
simpleEnricher(simpleFixture)
);
expect(res).toEqual({
items: [
{
monitor_id: 'foo',
state: { summary: {}, timestamp: '1999-12-31T00:00:00.000Z' },
expect(res).toMatchInlineSnapshot(`
Object {
"items": Array [
Object {
"monitor_id": "foo",
"state": Object {
"summary": Object {},
"timestamp": "946598400000",
"url": Object {},
},
},
Object {
"monitor_id": "bar",
"state": Object {
"summary": Object {},
"timestamp": "946598400000",
"url": Object {},
},
},
],
"nextPagePagination": Object {
"cursorDirection": "AFTER",
"cursorKey": "bar",
"sortOrder": "ASC",
},
{ monitor_id: 'bar', state: { summary: {}, timestamp: '1999-12-31T00:00:00.000Z' } },
],
nextPagePagination: { cursorDirection: 'AFTER', sortOrder: 'ASC', cursorKey: 'bar' },
prevPagePagination: { cursorDirection: 'BEFORE', sortOrder: 'ASC', cursorKey: 'foo' },
});
"prevPagePagination": Object {
"cursorDirection": "BEFORE",
"cursorKey": "foo",
"sortOrder": "ASC",
},
}
`);
});
});

View file

@ -9,7 +9,7 @@ import { CursorPagination } from '../types';
import {
CursorDirection,
SortOrder,
} from '../../../../../../../legacy/plugins/uptime/common/graphql/types';
} from '../../../../../../../legacy/plugins/uptime/common/runtime_types';
describe(QueryContext, () => {
// 10 minute range

View file

@ -8,7 +8,7 @@ import { CursorPagination } from '../types';
import {
CursorDirection,
SortOrder,
} from '../../../../../../../legacy/plugins/uptime/common/graphql/types';
} from '../../../../../../../legacy/plugins/uptime/common/runtime_types';
import { QueryContext } from '../query_context';
export const prevPagination = (key: any): CursorPagination => {

View file

@ -8,12 +8,12 @@ import { get, sortBy } from 'lodash';
import { QueryContext } from './query_context';
import { QUERY, STATES } from '../../../../../../legacy/plugins/uptime/common/constants';
import {
MonitorSummary,
SummaryHistogram,
Check,
Histogram,
MonitorSummary,
CursorDirection,
SortOrder,
} from '../../../../../../legacy/plugins/uptime/common/graphql/types';
} from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { MonitorEnricher } from './fetch_page';
export const enrichMonitorGroups: MonitorEnricher = async (
@ -250,11 +250,8 @@ export const enrichMonitorGroups: MonitorEnricher = async (
const summaries: MonitorSummary[] = monitorBuckets.map((monitor: any) => {
const monitorId = get<string>(monitor, 'key.monitor_id');
monitorIds.push(monitorId);
let state = get<any>(monitor, 'state.value');
state = {
...state,
timestamp: state['@timestamp'],
};
const state: any = monitor.state?.value;
state.timestamp = state['@timestamp'];
const { checks } = state;
if (checks) {
state.checks = sortBy<SortChecks, Check>(checks, checksSortBy);
@ -289,7 +286,7 @@ export const enrichMonitorGroups: MonitorEnricher = async (
const getHistogramForMonitors = async (
queryContext: QueryContext,
monitorIds: string[]
): Promise<{ [key: string]: SummaryHistogram }> => {
): Promise<{ [key: string]: Histogram }> => {
const params = {
index: queryContext.heartbeatIndices,
body: {

View file

@ -12,7 +12,7 @@ import {
CursorDirection,
MonitorSummary,
SortOrder,
} from '../../../../../../legacy/plugins/uptime/common/graphql/types';
} from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { enrichMonitorGroups } from './enrich_monitor_groups';
import { MonitorGroupIterator } from './monitor_group_iterator';

View file

@ -5,7 +5,7 @@
*/
import { get, set } from 'lodash';
import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/graphql/types';
import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { QueryContext } from './query_context';
// This is the first phase of the query. In it, we find the most recent check groups that matched the given query.

View file

@ -6,7 +6,7 @@
import { QueryContext } from './query_context';
import { fetchChunk } from './fetch_chunk';
import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/graphql/types';
import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { MonitorGroups } from './fetch_page';
import { CursorPagination } from './types';

View file

@ -5,7 +5,7 @@
*/
import { QueryContext } from './query_context';
import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/graphql/types';
import { CursorDirection } from '../../../../../../legacy/plugins/uptime/common/runtime_types';
import { MonitorGroups, MonitorLocCheckGroup } from './fetch_page';
/**

View file

@ -7,7 +7,7 @@
import {
CursorDirection,
SortOrder,
} from '../../../../../../legacy/plugins/uptime/common/graphql/types';
} from '../../../../../../legacy/plugins/uptime/common/runtime_types';
export interface CursorPagination {
cursorKey?: any;

View file

@ -12,6 +12,7 @@ import { createGetSnapshotCount } from './snapshot';
import { UMRestApiRouteFactory } from './types';
import {
createGetMonitorDetailsRoute,
createMonitorListRoute,
createGetMonitorLocationsRoute,
createGetStatusBarRoute,
} from './monitors';
@ -30,6 +31,7 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [
createPostDynamicSettingsRoute,
createGetMonitorDetailsRoute,
createGetMonitorLocationsRoute,
createMonitorListRoute,
createGetStatusBarRoute,
createGetSnapshotCount,
createLogPageViewRoute,

View file

@ -5,5 +5,6 @@
*/
export { createGetMonitorDetailsRoute } from './monitors_details';
export { createMonitorListRoute } from './monitor_list';
export { createGetMonitorLocationsRoute } from './monitor_locations';
export { createGetStatusBarRoute } from './monitor_status';

View file

@ -0,0 +1,69 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import { UMRestApiRouteFactory } from '../types';
import { CONTEXT_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants';
import { API_URLS } from '../../../../../legacy/plugins/uptime/common/constants/rest_api';
export const createMonitorListRoute: UMRestApiRouteFactory = libs => ({
method: 'GET',
path: API_URLS.MONITOR_LIST,
validate: {
query: schema.object({
dateRangeStart: schema.string(),
dateRangeEnd: schema.string(),
filters: schema.maybe(schema.string()),
pagination: schema.maybe(schema.string()),
statusFilter: schema.maybe(schema.string()),
pageSize: schema.number(),
}),
},
options: {
tags: ['access:uptime-read'],
},
handler: async ({ callES, dynamicSettings }, _context, request, response): Promise<any> => {
const {
dateRangeStart,
dateRangeEnd,
filters,
pagination,
statusFilter,
pageSize,
} = request.query;
const decodedPagination = pagination
? JSON.parse(decodeURIComponent(pagination))
: CONTEXT_DEFAULTS.CURSOR_PAGINATION;
const [indexStatus, { summaries, nextPagePagination, prevPagePagination }] = await Promise.all([
libs.requests.getIndexStatus({ callES, dynamicSettings }),
libs.requests.getMonitorStates({
callES,
dynamicSettings,
dateRangeStart,
dateRangeEnd,
pagination: decodedPagination,
pageSize,
filters,
// this is added to make typescript happy,
// this sort of reassignment used to be further downstream but I've moved it here
// because this code is going to be decomissioned soon
statusFilter: statusFilter || undefined,
}),
]);
const totalSummaryCount = indexStatus?.docCount ?? 0;
return response.ok({
body: {
summaries,
nextPagePagination,
prevPagePagination,
totalSummaryCount,
},
});
},
});

View file

@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { makeExecutableSchema } from 'graphql-tools';
import { DEFAULT_GRAPHQL_PATH, resolvers, typeDefs } from './graphql';
import { UMServerLibs } from './lib/lib';
import { createRouteWithAuth, restApiRoutes, uptimeRouteWrapper } from './rest_api';
import { UptimeCoreSetup, UptimeCorePlugins } from './lib/adapters';
@ -23,10 +21,4 @@ export const initUptimeServer = (
uptimeAlertTypeFactories.forEach(alertTypeFactory =>
plugins.alerting.registerType(alertTypeFactory(server, libs))
);
const graphQLSchema = makeExecutableSchema({
resolvers: resolvers.map(createResolversFn => createResolversFn(libs)),
typeDefs,
});
libs.framework.registerGraphQLEndpoint(DEFAULT_GRAPHQL_PATH, graphQLSchema);
};

View file

@ -26,17 +26,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
expect(result.response).to.have.property('statusCode', 200);
};
const executeRESTAPIQuery = async (username: string, password: string, spaceId?: string) => {
const basePath = spaceId ? `/s/${spaceId}` : '';
return await supertest
.get(basePath + API_URLS.INDEX_STATUS)
.auth(username, password)
.set('kbn-xsrf', 'foo')
.then((response: any) => ({ error: undefined, response }))
.catch((error: any) => ({ error, response: undefined }));
};
const executePingsRequest = async (username: string, password: string, spaceId?: string) => {
const basePath = spaceId ? `/s/${spaceId}` : '';
@ -72,9 +61,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
full_name: 'a kibana user',
});
const graphQLResult = await executeRESTAPIQuery(username, password);
expect404(graphQLResult);
const pingsResult = await executePingsRequest(username, password);
expect404(pingsResult);
} finally {
@ -111,9 +97,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
full_name: 'a kibana user',
});
const graphQLResult = await executeRESTAPIQuery(username, password);
expectResponse(graphQLResult);
const pingsResult = await executePingsRequest(username, password);
expectResponse(pingsResult);
} finally {
@ -153,9 +136,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
full_name: 'a kibana user',
});
const graphQLResult = await executeRESTAPIQuery(username, password);
expect404(graphQLResult);
const pingsResult = await executePingsRequest(username, password);
expect404(pingsResult);
} finally {
@ -222,17 +202,11 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
});
it('user_1 can access APIs in space_1', async () => {
const graphQLResult = await executeRESTAPIQuery(username, password, space1Id);
expectResponse(graphQLResult);
const pingsResult = await executePingsRequest(username, password, space1Id);
expectResponse(pingsResult);
});
it(`user_1 can't access APIs in space_2`, async () => {
const graphQLResult = await executeRESTAPIQuery(username, password);
expect404(graphQLResult);
const pingsResult = await executePingsRequest(username, password);
expect404(pingsResult);
});

Some files were not shown because too many files have changed in this diff Show more