[Synthetics] Status overview embeddable (#188807)

## Summary

Added status overview embeddable !!


https://github.com/user-attachments/assets/27499ecf-549f-43a6-a16b-22a44db36814

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2024-07-22 14:21:50 +02:00 committed by GitHub
parent bc42310da5
commit eb71438b9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 738 additions and 337 deletions

View file

@ -17,6 +17,7 @@
"embeddable",
"discover",
"dataViews",
"dashboard",
"encryptedSavedObjects",
"exploratoryView",
"features",
@ -31,7 +32,9 @@
"triggersActionsUi",
"usageCollection",
"bfetch",
"unifiedSearch"
"uiActions",
"unifiedSearch",
"presentationUtil"
],
"optionalPlugins": [
"cloud",
@ -53,7 +56,7 @@
"observability",
"spaces",
"indexLifecycleManagement",
"unifiedDocViewer"
"unifiedDocViewer",
]
}
}

View file

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

View file

@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { CoreSetup } from '@kbn/core-lifecycle-browser';
import { ADD_PANEL_TRIGGER } from '@kbn/ui-actions-browser/src';
import { createStatusOverviewPanelAction } from './ui_actions/create_overview_panel_action';
import { ClientPluginsSetup, ClientPluginsStart } from '../../plugin';
import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from './constants';
export const registerSyntheticsEmbeddables = (
core: CoreSetup<ClientPluginsStart, unknown>,
pluginsSetup: ClientPluginsSetup
) => {
pluginsSetup.embeddable.registerReactEmbeddableFactory(
SYNTHETICS_OVERVIEW_EMBEDDABLE,
async () => {
const { getStatusOverviewEmbeddableFactory } = await import(
'./status_overview/status_overview_embeddable_factory'
);
return getStatusOverviewEmbeddableFactory(core.getStartServices);
}
);
const { uiActions, cloud, serverless } = pluginsSetup;
// Initialize actions
const addOverviewPanelAction = createStatusOverviewPanelAction();
core.getStartServices().then(([_, pluginsStart]) => {
pluginsStart.dashboard.registerDashboardPanelPlacementSetting(
SYNTHETICS_OVERVIEW_EMBEDDABLE,
() => {
return { width: 10, height: 8 };
}
);
});
// Assign triggers
// Only register these actions in stateful kibana, and the serverless observability project
if (Boolean((serverless && cloud?.serverless.projectType === 'observability') || !serverless)) {
uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addOverviewPanelAction);
}
};

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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { Subject } from 'rxjs';
import { OverviewStatus } from '../../synthetics/components/monitors_page/overview/overview/overview_status';
import { SyntheticsEmbeddableContext } from '../synthetics_embeddable_context';
export const StatusOverviewComponent = ({ reload$ }: { reload$: Subject<boolean> }) => {
return (
<SyntheticsEmbeddableContext>
<OverviewStatus reload$={reload$} />
</SyntheticsEmbeddableContext>
);
};

View file

@ -0,0 +1,99 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import React, { useEffect } from 'react';
import { DefaultEmbeddableApi, ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import {
initializeTitles,
useBatchedPublishingSubjects,
fetch$,
PublishesWritablePanelTitle,
PublishesPanelTitle,
SerializedTitles,
} from '@kbn/presentation-publishing';
import { BehaviorSubject, Subject } from 'rxjs';
import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser';
import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from '../constants';
import { ClientPluginsStart } from '../../../plugin';
import { StatusOverviewComponent } from './status_overview_component';
export const getOverviewPanelTitle = () =>
i18n.translate('xpack.synthetics.statusOverview.displayName', {
defaultMessage: 'Synthetics Status Overview',
});
export type OverviewEmbeddableState = SerializedTitles;
export type StatusOverviewApi = DefaultEmbeddableApi<OverviewEmbeddableState> &
PublishesWritablePanelTitle &
PublishesPanelTitle;
export const getStatusOverviewEmbeddableFactory = (
getStartServices: StartServicesAccessor<ClientPluginsStart>
) => {
const factory: ReactEmbeddableFactory<
OverviewEmbeddableState,
OverviewEmbeddableState,
StatusOverviewApi
> = {
type: SYNTHETICS_OVERVIEW_EMBEDDABLE,
deserializeState: (state) => {
return state.rawState as OverviewEmbeddableState;
},
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
const defaultTitle$ = new BehaviorSubject<string | undefined>(getOverviewPanelTitle());
const reload$ = new Subject<boolean>();
const api = buildApi(
{
...titlesApi,
defaultPanelTitle: defaultTitle$,
serializeState: () => {
return {
rawState: {
...serializeTitles(),
},
};
},
},
{
...titleComparators,
}
);
const fetchSubscription = fetch$(api)
.pipe()
.subscribe((next) => {
reload$.next(next.isReload);
});
return {
api,
Component: () => {
const [] = useBatchedPublishingSubjects();
useEffect(() => {
return () => {
fetchSubscription.unsubscribe();
};
}, []);
return (
<div
data-shared-item="" // TODO: Remove data-shared-item and data-rendering-count as part of https://github.com/elastic/kibana/issues/179376
>
<StatusOverviewComponent reload$={reload$} />
</div>
);
},
};
},
};
return factory;
};

View file

@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { createBrowserHistory } from 'history';
import { EuiPanel } from '@elastic/eui';
import { Router } from '@kbn/shared-ux-router';
import { SyntheticsSharedContext } from '../synthetics/contexts/synthetics_shared_context';
import { SyntheticsEmbeddableStateContextProvider } from '../synthetics/contexts/synthetics_embeddable_context';
import { getSyntheticsAppProps } from '../synthetics/render_app';
import { SyntheticsSettingsContextProvider } from '../synthetics/contexts';
export const SyntheticsEmbeddableContext: React.FC<{ search?: string }> = ({
search,
children,
}) => {
const props = getSyntheticsAppProps();
return (
<SyntheticsSharedContext {...props}>
<SyntheticsEmbeddableStateContextProvider>
<Router history={createBrowserHistory()}>
<SyntheticsSettingsContextProvider {...props}>
<EuiPanel hasShadow={false}>{children}</EuiPanel>
</SyntheticsSettingsContextProvider>
</Router>
</SyntheticsEmbeddableStateContextProvider>
</SyntheticsSharedContext>
);
};

View file

@ -0,0 +1,54 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import {
IncompatibleActionError,
type UiActionsActionDefinition,
} from '@kbn/ui-actions-plugin/public';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from '../constants';
export const COMMON_SYNTHETICS_GROUPING = [
{
id: 'synthetics',
getDisplayName: () =>
i18n.translate('xpack.synthetics.common.constants.grouping.legacy', {
defaultMessage: 'Synthetics',
}),
getIconType: () => {
return 'online';
},
},
];
export const ADD_SYNTHETICS_OVERVIEW_ACTION_ID = 'CREATE_SYNTHETICS_OVERVIEW_EMBEDDABLE';
export function createStatusOverviewPanelAction(): UiActionsActionDefinition<EmbeddableApiContext> {
return {
id: ADD_SYNTHETICS_OVERVIEW_ACTION_ID,
grouping: COMMON_SYNTHETICS_GROUPING,
order: 30,
getIconType: () => 'online',
isCompatible: async ({ embeddable }) => {
return apiIsPresentationContainer(embeddable);
},
execute: async ({ embeddable }) => {
if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError();
try {
embeddable.addNewPanel({
panelType: SYNTHETICS_OVERVIEW_EMBEDDABLE,
});
} catch (e) {
return Promise.reject();
}
},
getDisplayName: () =>
i18n.translate('xpack.synthetics.syntheticsEmbeddable.ariaLabel', {
defaultMessage: 'Synthetics Overview',
}),
};
}

View file

@ -14,6 +14,8 @@ import { useFetcher } from '@kbn/observability-shared-plugin/public';
import { useSessionStorage } from 'react-use';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ClientPluginsStart } from '../../../../../plugin';
import { selectDynamicSettings } from '../../../state/settings';
import {
selectSyntheticsAlerts,
@ -21,7 +23,7 @@ import {
} from '../../../state/alert_rules/selectors';
import { selectMonitorListState } from '../../../state';
import { getDynamicSettingsAction } from '../../../state/settings/actions';
import { useSyntheticsSettingsContext, useSyntheticsStartPlugins } from '../../../contexts';
import { useSyntheticsSettingsContext } from '../../../contexts';
import { ConfigKey } from '../../../../../../common/runtime_types';
export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boolean }) => {
@ -40,7 +42,8 @@ export const AlertingCallout = ({ isAlertingEnabled }: { isAlertingEnabled?: boo
loaded: monitorsLoaded,
} = useSelector(selectMonitorListState);
const syntheticsLocators = useSyntheticsStartPlugins()?.share?.url.locators;
const syntheticsLocators = useKibana<ClientPluginsStart>().services.share?.url.locators;
const locator = syntheticsLocators?.get(syntheticsSettingsLocatorID);
const { data: url } = useFetcher(() => {

View file

@ -0,0 +1,139 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { FC, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiButtonIcon,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiPopover,
EuiProgress,
EuiTitle,
} from '@elastic/eui';
import {
LazySavedObjectSaveModalDashboard,
SaveModalDashboardProps,
withSuspense,
} from '@kbn/presentation-util-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { SYNTHETICS_OVERVIEW_EMBEDDABLE } from '../../../../embeddables/constants';
import { ClientPluginsStart } from '../../../../../plugin';
const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard);
export const EmbeddablePanelWrapper: FC<{
title: string;
loading?: boolean;
}> = ({ children, title, loading }) => {
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
const [isDashboardAttachmentReady, setDashboardAttachmentReady] = React.useState(false);
const closePopover = () => {
setIsPopoverOpen(false);
};
const { embeddable } = useKibana<ClientPluginsStart>().services;
const isSyntheticsApp = window.location.pathname.includes('/app/synthetics');
const handleAttachToDashboardSave: SaveModalDashboardProps['onSave'] = useCallback(
({ dashboardId, newTitle, newDescription }) => {
const stateTransfer = embeddable.getStateTransfer();
const embeddableInput = {};
const state = {
input: embeddableInput,
type: SYNTHETICS_OVERVIEW_EMBEDDABLE,
};
const path = dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`;
stateTransfer.navigateToWithEmbeddablePackage('dashboards', {
state,
path,
});
},
[embeddable]
);
return (
<>
<EuiPanel hasShadow={false} hasBorder>
{loading && <EuiProgress size="xs" color="accent" />}
<EuiFlexGroup>
<EuiFlexItem grow={true}>
<EuiTitle size="xs">
<h3>{title}</h3>
</EuiTitle>
</EuiFlexItem>
{isSyntheticsApp && (
<EuiFlexItem grow={false}>
<EuiPopover
button={
<EuiButtonIcon
color="text"
data-test-subj="syntheticsEmbeddablePanelWrapperButton"
iconType="boxesHorizontal"
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
/>
}
isOpen={isPopoverOpen}
closePopover={closePopover}
>
<EuiContextMenuPanel
size="s"
items={[
<EuiContextMenuItem
key="share"
icon="dashboardApp"
onClick={() => {
setDashboardAttachmentReady(true);
closePopover();
}}
>
{i18n.translate(
'xpack.synthetics.embeddablePanelWrapper.shareContextMenuItemLabel',
{ defaultMessage: 'Add to dashboard' }
)}
</EuiContextMenuItem>,
]}
/>
</EuiPopover>
</EuiFlexItem>
)}
</EuiFlexGroup>
{children}
</EuiPanel>
{isDashboardAttachmentReady ? (
<SavedObjectSaveModalDashboard
objectType={i18n.translate(
'xpack.synthetics.item.actions.addToDashboard.objectTypeLabel',
{
defaultMessage: 'Status Overview',
}
)}
documentInfo={{
title: i18n.translate('xpack.synthetics.item.actions.addToDashboard.attachmentTitle', {
defaultMessage: 'Status Overview',
}),
}}
canSaveByReference={false}
onClose={() => {
setDashboardAttachmentReady(false);
}}
onSave={handleAttachToDashboardSave}
/>
) : null}
</>
);
};

View file

@ -7,20 +7,18 @@
import React, { useContext, useEffect } from 'react';
import { EuiSuperDatePicker } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ClientPluginsStart } from '../../../../../plugin';
import { useUrlParams } from '../../../hooks';
import { CLIENT_DEFAULTS } from '../../../../../../common/constants';
import {
SyntheticsSettingsContext,
SyntheticsStartupPluginsContext,
SyntheticsRefreshContext,
} from '../../../contexts';
import { SyntheticsSettingsContext, SyntheticsRefreshContext } from '../../../contexts';
export const SyntheticsDatePicker = ({ fullWidth }: { fullWidth?: boolean }) => {
const [getUrlParams, updateUrl] = useUrlParams();
const { commonlyUsedRanges } = useContext(SyntheticsSettingsContext);
const { refreshApp } = useContext(SyntheticsRefreshContext);
const { data } = useContext(SyntheticsStartupPluginsContext);
const { data } = useKibana<ClientPluginsStart>().services;
// read time from state and update the url
const sharedTimeState = data?.query.timefilter.timefilter.getTime();

View file

@ -12,9 +12,9 @@ import moment from 'moment';
import { AllSeries, createExploratoryViewUrl } from '@kbn/exploratory-view-plugin/public';
import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ClientPluginsStart } from '../../../../../plugin';
import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants';
import { JourneyStep } from '../../../../../../common/runtime_types';
import { useSyntheticsStartPlugins } from '../../../contexts';
export const getLast48Intervals = (activeStep: JourneyStep) => {
const timestamp = activeStep['@timestamp'];
@ -36,7 +36,7 @@ export function StepFieldTrend({
field: string;
step: JourneyStep;
}) {
const { exploratoryView } = useSyntheticsStartPlugins();
const exploratoryView = useKibana<ClientPluginsStart>().services.exploratoryView;
const EmbeddableExpView = exploratoryView!.ExploratoryViewEmbeddable;

View file

@ -179,7 +179,7 @@ describe('GettingStartedPage', () => {
});
// page is loaded
expect(kibanaService.core.application.navigateToApp).toHaveBeenCalledWith('synthetics', {
expect(kibanaService.coreStart.application.navigateToApp).toHaveBeenCalledWith('synthetics', {
path: '/monitors',
});
});

View file

@ -56,14 +56,14 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData
}, [monitorData]);
useEffect(() => {
const { core, toasts } = kibanaService;
const { coreStart, toasts } = kibanaService;
const newMonitor = data as UpsertMonitorResponse;
const hasErrors = data && 'attributes' in data && data.attributes.errors?.length > 0;
if (hasErrors && !loading) {
showSyncErrors(
(data as { attributes: { errors: ServiceLocationErrors } })?.attributes.errors ?? [],
serviceLocations,
core
coreStart
);
}

View file

@ -45,7 +45,7 @@ export const useMonitorSave = ({ monitorData }: { monitorData?: SyntheticsMonito
}, [monitorData]);
useEffect(() => {
const { core, toasts } = kibanaService;
const { coreStart, toasts } = kibanaService;
if (status === FETCH_STATUS.FAILURE && error) {
toasts.addError(
@ -64,7 +64,7 @@ export const useMonitorSave = ({ monitorData }: { monitorData?: SyntheticsMonito
<p data-test-subj="synthetcsMonitorSaveSubtext">
{monitorId ? MONITOR_UPDATED_SUCCESS_LABEL_SUBTEXT : MONITOR_SUCCESS_LABEL_SUBTEXT}
</p>,
core
coreStart
),
toastLifeTimeMs: 3000,
});

View file

@ -18,7 +18,7 @@ import {
export function useOverviewStatus({ scopeStatusByLocation }: { scopeStatusByLocation: boolean }) {
const pageState = useSelector(selectOverviewPageState);
const { status, error, loaded } = useSelector(selectOverviewStatus);
const { status, error, loaded, loading } = useSelector(selectOverviewStatus);
const { lastRefresh } = useSyntheticsRefreshContext();
@ -37,5 +37,6 @@ export function useOverviewStatus({ scopeStatusByLocation }: { scopeStatusByLoca
return {
status,
error,
loading,
};
}

View file

@ -42,7 +42,7 @@ export const DeleteMonitor = ({
}, [configId, isDeleting]);
useEffect(() => {
const { core, toasts } = kibanaService;
const { coreStart, toasts } = kibanaService;
if (!isDeleting) {
return;
}
@ -53,7 +53,7 @@ export const DeleteMonitor = ({
<p data-test-subj="uptimeDeleteMonitorFailure">
{labels.MONITOR_DELETE_FAILURE_LABEL}
</p>,
core
coreStart
),
},
{ toastLifeTimeMs: 3000 }
@ -72,7 +72,7 @@ export const DeleteMonitor = ({
}
)}
</p>,
core
coreStart
),
},
{ toastLifeTimeMs: 3000 }

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState, useRef, memo, useCallback } from 'react';
import React, { useState, useRef, memo, useCallback, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { i18n } from '@kbn/i18n';
import {
@ -15,11 +15,12 @@ import {
EuiButtonEmpty,
EuiText,
} from '@elastic/eui';
import { selectOverviewStatus } from '../../../../state/overview_status';
import { useOverviewStatus } from '../../hooks/use_overview_status';
import { useInfiniteScroll } from './use_infinite_scroll';
import { GridItemsByGroup } from './grid_by_group/grid_items_by_group';
import { GroupFields } from './grid_by_group/group_fields';
import {
fetchMonitorOverviewAction,
quietFetchOverviewAction,
selectOverviewState,
setFlyoutConfig,
@ -33,7 +34,7 @@ import { NoMonitorsFound } from '../../common/no_monitors_found';
import { MonitorDetailFlyout } from './monitor_detail_flyout';
export const OverviewGrid = memo(() => {
const { status } = useSelector(selectOverviewStatus);
const { status } = useOverviewStatus({ scopeStatusByLocation: true });
const {
data: { monitors },
@ -49,6 +50,11 @@ export const OverviewGrid = memo(() => {
const intersectionRef = useRef(null);
const { monitorsSortedByStatus } = useMonitorsSortedByStatus();
// fetch overview for all other page state changes
useEffect(() => {
dispatch(fetchMonitorOverviewAction.get(pageState));
}, [dispatch, pageState]);
const setFlyoutConfigCallback = useCallback(
(params: FlyoutParamProps) => dispatch(setFlyoutConfig(params)),
[dispatch]

View file

@ -5,10 +5,13 @@
* 2.0.
*/
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiStat, EuiTitle } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiStat } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Subject } from 'rxjs';
import { useSyntheticsRefreshContext } from '../../../../contexts';
import { EmbeddablePanelWrapper } from '../../../common/components/embeddable_panel_wrapper';
import { clearOverviewStatusErrorAction } from '../../../../state/overview_status';
import { kibanaService } from '../../../../../../utils/kibana_service';
import { useGetUrlParams } from '../../../../hooks/use_url_params';
@ -18,10 +21,16 @@ function title(t?: number) {
return t ?? '-';
}
export function OverviewStatus() {
export function OverviewStatus({ reload$ }: { reload$?: Subject<boolean> }) {
const { statusFilter } = useGetUrlParams();
const { status, error: statusError } = useOverviewStatus({ scopeStatusByLocation: true });
const { refreshApp } = useSyntheticsRefreshContext();
const {
status,
error: statusError,
loading,
} = useOverviewStatus({ scopeStatusByLocation: true });
const dispatch = useDispatch();
const [statusConfig, setStatusConfig] = useState({
up: status?.up,
@ -30,6 +39,14 @@ export function OverviewStatus() {
disabledCount: status?.disabledCount,
});
useEffect(() => {
const sub = reload$?.subscribe(() => {
refreshApp();
});
return () => sub?.unsubscribe();
}, [refreshApp, reload$]);
useEffect(() => {
if (statusError) {
dispatch(clearOverviewStatusErrorAction());
@ -87,10 +104,7 @@ export function OverviewStatus() {
}, [status, statusFilter]);
return (
<EuiPanel hasShadow={false} hasBorder>
<EuiTitle size="xs">
<h3>{headingText}</h3>
</EuiTitle>
<EmbeddablePanelWrapper title={headingText} loading={loading}>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem grow={false}>
@ -136,7 +150,7 @@ export function OverviewStatus() {
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiPanel>
</EmbeddablePanelWrapper>
);
}

View file

@ -48,7 +48,7 @@ export const DeleteParam = ({
if (!isDeleting) {
return;
}
const { core, toasts } = kibanaService;
const { coreStart, toasts } = kibanaService;
if (status === FETCH_STATUS.FAILURE) {
toasts.addDanger(
@ -61,7 +61,7 @@ export const DeleteParam = ({
values: { name },
})}
</p>,
core
coreStart
),
},
{ toastLifeTimeMs: 3000 }
@ -76,7 +76,7 @@ export const DeleteParam = ({
values: { name },
})}
</p>,
core
coreStart
),
},
{ toastLifeTimeMs: 3000 }

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import React from 'react';
import { SyntheticsStartupPluginsContext } from '../../../../../contexts';
import { i18n } from '@kbn/i18n';
import { JourneyStep } from '../../../../../../../../common/runtime_types';
import { WaterfallContext } from '../context/waterfall_context';
@ -25,9 +26,21 @@ const EmbeddableMock = ({
}) => (
<div>
<h1>{title}</h1>
<div aria-label="append title">{appendTitle}</div>
<div
aria-label={i18n.translate('xpack.synthetics.embeddableMock.div.appendTitleLabel', {
defaultMessage: 'append title',
})}
>
{appendTitle}
</div>
<div>{reportType}</div>
<div aria-label="attributes">{JSON.stringify(attributes)}</div>
<div
aria-label={i18n.translate('xpack.synthetics.embeddableMock.div.attributesLabel', {
defaultMessage: 'attributes',
})}
>
{JSON.stringify(attributes)}
</div>
</div>
);
@ -40,9 +53,8 @@ export const TestWrapper = ({
activeStep?: JourneyStep;
children: JSX.Element;
}) => (
<SyntheticsStartupPluginsContext.Provider
value={{
// @ts-expect-error incomplete implementation for test purposes
<KibanaContextProvider
services={{
exploratoryView: {
ExploratoryViewEmbeddable: jest.fn((props: any) => <EmbeddableMock {...props} />),
},
@ -55,5 +67,5 @@ export const TestWrapper = ({
>
{children}
</WaterfallContext.Provider>
</SyntheticsStartupPluginsContext.Provider>
</KibanaContextProvider>
);

View file

@ -38,7 +38,7 @@ export const BrowserTestRunResult = ({
});
useEffect(() => {
const { core, toasts } = kibanaService;
const { coreStart, toasts } = kibanaService;
if (retriesExceeded) {
toasts.addDanger(
{
@ -49,7 +49,7 @@ export const BrowserTestRunResult = ({
defaultMessage="Manual test run failed for {name}"
values={{ name }}
/>,
core
coreStart
),
},
{

View file

@ -25,7 +25,7 @@ export function SimpleTestResults({ name, testRunId, expectPings, onDone }: Prop
useEffect(() => {
if (retriesExceeded) {
const { core, toasts } = kibanaService;
const { coreStart, toasts } = kibanaService;
toasts.addDanger(
{
@ -36,7 +36,7 @@ export function SimpleTestResults({ name, testRunId, expectPings, onDone }: Prop
defaultMessage="Manual test run failed for {name}"
values={{ name }}
/>,
core
coreStart
),
},
{

View file

@ -7,5 +7,3 @@
export * from './synthetics_refresh_context';
export * from './synthetics_settings_context';
export * from './synthetics_theme_context';
export * from './synthetics_startup_plugins_context';

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { createContext, useContext, useMemo } from 'react';
import { History, createMemoryHistory } from 'history';
interface SyntheticsEmbeddableContext {
history: History;
}
const defaultContext: SyntheticsEmbeddableContext = {} as SyntheticsEmbeddableContext;
export const SyntheticsEmbeddableContext = createContext(defaultContext);
export const SyntheticsEmbeddableStateContextProvider: React.FC = ({ children }) => {
const value = useMemo(() => {
return { history: createMemoryHistory() };
}, []);
return <SyntheticsEmbeddableContext.Provider value={value} children={children} />;
};
export const useSyntheticsEmbeddableContext = () => useContext(SyntheticsEmbeddableContext);

View file

@ -27,13 +27,13 @@ export interface CommonlyUsedDateRange {
export interface SyntheticsAppProps {
basePath: string;
canSave: boolean;
core: CoreStart;
coreStart: CoreStart;
darkMode: boolean;
i18n: I18nStart;
isApmAvailable: boolean;
isInfraAvailable: boolean;
isLogsAvailable: boolean;
plugins: ClientPluginsSetup;
setupPlugins: ClientPluginsSetup;
startPlugins: ClientPluginsStart;
setBadge: (badge?: ChromeBadge) => void;
renderGlobalHelpControls(): void;

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { Provider as ReduxProvider } from 'react-redux';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { SyntheticsRefreshContextProvider } from './synthetics_refresh_context';
import { SyntheticsDataViewContextProvider } from './synthetics_data_view_context';
import { SyntheticsAppProps } from './synthetics_settings_context';
import { storage, store } from '../state';
export const SyntheticsSharedContext: React.FC<SyntheticsAppProps> = ({
coreStart,
setupPlugins,
startPlugins,
children,
darkMode,
}) => {
return (
<KibanaContextProvider
services={{
...coreStart,
...setupPlugins,
storage,
data: startPlugins.data,
inspector: startPlugins.inspector,
triggersActionsUi: startPlugins.triggersActionsUi,
observability: startPlugins.observability,
observabilityShared: startPlugins.observabilityShared,
observabilityAIAssistant: startPlugins.observabilityAIAssistant,
exploratoryView: startPlugins.exploratoryView,
cases: startPlugins.cases,
spaces: startPlugins.spaces,
fleet: startPlugins.fleet,
share: startPlugins.share,
unifiedSearch: startPlugins.unifiedSearch,
embeddable: startPlugins.embeddable,
}}
>
<EuiThemeProvider darkMode={darkMode}>
<ReduxProvider store={store}>
<SyntheticsRefreshContextProvider>
<SyntheticsDataViewContextProvider dataViews={startPlugins.dataViews}>
<RedirectAppLinks
coreStart={{
application: coreStart.application,
}}
>
{children}
</RedirectAppLinks>
</SyntheticsDataViewContextProvider>
</SyntheticsRefreshContextProvider>
</ReduxProvider>
</EuiThemeProvider>
</KibanaContextProvider>
);
};

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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { createContext, useContext, PropsWithChildren } from 'react';
import { ClientPluginsStart } from '../../../plugin';
export const SyntheticsStartupPluginsContext = createContext<Partial<ClientPluginsStart>>({});
export const SyntheticsStartupPluginsContextProvider: React.FC<
PropsWithChildren<Partial<ClientPluginsStart>>
> = ({ children, ...props }) => (
<SyntheticsStartupPluginsContext.Provider value={{ ...props }} children={children} />
);
export const useSyntheticsStartPlugins = () => useContext(SyntheticsStartupPluginsContext);

View file

@ -1,97 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { euiLightVars, euiDarkVars } from '@kbn/ui-theme';
import React, { createContext, useContext, useMemo, FC, PropsWithChildren } from 'react';
import { DARK_THEME, LIGHT_THEME, PartialTheme, Theme } from '@elastic/charts';
export interface SyntheticsAppColors {
danger: string;
dangerBehindText: string;
success: string;
gray: string;
range: string;
mean: string;
warning: string;
lightestShade: string;
}
export interface SyntheticsThemeContextValues {
colors: SyntheticsAppColors;
chartTheme: {
baseTheme?: Theme;
theme?: PartialTheme;
};
}
/**
* These are default values for the context. These defaults are typically
* overwritten by the Synthetics App upon its invocation.
*/
const defaultContext: SyntheticsThemeContextValues = {
colors: {
danger: euiLightVars.euiColorDanger,
dangerBehindText: euiDarkVars.euiColorVis9_behindText,
mean: euiLightVars.euiColorPrimary,
range: euiLightVars.euiFocusBackgroundColor,
success: euiLightVars.euiColorSuccess,
warning: euiLightVars.euiColorWarning,
gray: euiLightVars.euiColorLightShade,
lightestShade: euiLightVars.euiColorLightestShade,
},
chartTheme: {
baseTheme: LIGHT_THEME,
},
};
export const SyntheticsThemeContext = createContext(defaultContext);
interface ThemeContextProps {
darkMode: boolean;
}
export const SyntheticsThemeContextProvider: FC<PropsWithChildren<ThemeContextProps>> = ({
darkMode,
children,
}) => {
let colors: SyntheticsAppColors;
if (darkMode) {
colors = {
danger: euiDarkVars.euiColorVis9,
dangerBehindText: euiDarkVars.euiColorVis9_behindText,
mean: euiDarkVars.euiColorPrimary,
gray: euiDarkVars.euiColorLightShade,
range: euiDarkVars.euiFocusBackgroundColor,
success: euiDarkVars.euiColorSuccess,
warning: euiDarkVars.euiColorWarning,
lightestShade: euiDarkVars.euiColorLightestShade,
};
} else {
colors = {
danger: euiLightVars.euiColorVis9,
dangerBehindText: euiLightVars.euiColorVis9_behindText,
mean: euiLightVars.euiColorPrimary,
gray: euiLightVars.euiColorLightShade,
range: euiLightVars.euiFocusBackgroundColor,
success: euiLightVars.euiColorSuccess,
warning: euiLightVars.euiColorWarning,
lightestShade: euiLightVars.euiColorLightestShade,
};
}
const value = useMemo(() => {
return {
colors,
chartTheme: {
baseTheme: darkMode ? DARK_THEME : LIGHT_THEME,
},
};
}, [colors, darkMode]);
return <SyntheticsThemeContext.Provider value={value} children={children} />;
};
export const useSyntheticsThemeContext = () => useContext(SyntheticsThemeContext);

View file

@ -8,7 +8,8 @@
import { useEffect, useState } from 'react';
import { LocatorClient } from '@kbn/share-plugin/common/url_service/locators';
import { syntheticsEditMonitorLocatorID } from '@kbn/observability-plugin/common';
import { useSyntheticsStartPlugins } from '../contexts';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ClientPluginsStart } from '../../../plugin';
export function useEditMonitorLocator({
configId,
@ -18,7 +19,7 @@ export function useEditMonitorLocator({
locators?: LocatorClient;
}) {
const [editUrl, setEditUrl] = useState<string | undefined>(undefined);
const syntheticsLocators = useSyntheticsStartPlugins()?.share?.url.locators;
const syntheticsLocators = useKibana<ClientPluginsStart>().services.share?.url.locators;
const locator = (locators || syntheticsLocators)?.get(syntheticsEditMonitorLocatorID);
useEffect(() => {

View file

@ -7,7 +7,8 @@
import { useEffect, useState } from 'react';
import { syntheticsMonitorDetailLocatorID } from '@kbn/observability-plugin/common';
import { useSyntheticsStartPlugins } from '../contexts';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ClientPluginsStart } from '../../../plugin';
export function useMonitorDetailLocator({
configId,
@ -17,7 +18,7 @@ export function useMonitorDetailLocator({
locationId?: string;
}) {
const [monitorUrl, setMonitorUrl] = useState<string | undefined>(undefined);
const locator = useSyntheticsStartPlugins()?.share?.url.locators.get(
const locator = useKibana<ClientPluginsStart>().services?.share?.url.locators.get(
syntheticsMonitorDetailLocatorID
);

View file

@ -19,19 +19,19 @@ import { store } from '../../../state';
import type { StatusRuleParams } from '../../../../../../common/rules/status_rule';
interface Props {
core: CoreStart;
coreStart: CoreStart;
plugins: ClientPluginsStart;
params: RuleTypeParamsExpressionProps<StatusRuleParams>;
}
// eslint-disable-next-line import/no-default-export
export default function MonitorStatusAlert({ core, plugins, params }: Props) {
kibanaService.core = core;
export default function MonitorStatusAlert({ coreStart, plugins, params }: Props) {
kibanaService.coreStart = coreStart;
const queryClient = new QueryClient();
return (
<ReduxProvider store={store}>
<QueryClientProvider client={queryClient}>
<KibanaContextProvider services={{ ...core, ...plugins }}>
<KibanaContextProvider services={{ ...coreStart, ...plugins }}>
<EuiText>
<FormattedMessage
id="xpack.synthetics.alertRule.monitorStatus.description"

View file

@ -17,18 +17,18 @@ import { kibanaService } from '../../../../../utils/kibana_service';
import { store } from '../../../state';
interface Props {
core: CoreStart;
coreStart: CoreStart;
plugins: ClientPluginsStart;
ruleParams: RuleTypeParamsExpressionProps<TLSParams>['ruleParams'];
setRuleParams: RuleTypeParamsExpressionProps<TLSParams>['setRuleParams'];
}
// eslint-disable-next-line import/no-default-export
export default function TLSAlert({ core, plugins, ruleParams, setRuleParams }: Props) {
kibanaService.core = core;
export default function TLSAlert({ coreStart, plugins, ruleParams, setRuleParams }: Props) {
kibanaService.coreStart = coreStart;
return (
<ReduxProvider store={store}>
<KibanaContextProvider services={{ ...core, ...plugins }}>
<KibanaContextProvider services={{ ...coreStart, ...plugins }}>
<TLSRuleComponent ruleParams={ruleParams} setRuleParams={setRuleParams} />
</KibanaContextProvider>
</ReduxProvider>

View file

@ -33,7 +33,7 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({
return `${docLinks.links.observability.syntheticsAlerting}`;
},
ruleParamsExpression: (paramProps: RuleTypeParamsExpressionProps<StatusRuleParams>) => (
<MonitorStatusAlert core={core} plugins={plugins} params={paramProps} />
<MonitorStatusAlert coreStart={core} plugins={plugins} params={paramProps} />
),
validate: (_ruleParams: StatusRuleParams) => {
return { errors: {} };

View file

@ -31,7 +31,7 @@ export const initTlsAlertType: AlertTypeInitializer = ({
},
ruleParamsExpression: (params: RuleTypeParamsExpressionProps<TLSParams>) => (
<TLSAlert
core={core}
coreStart={core}
plugins={plugins}
ruleParams={params.ruleParams}
setRuleParams={params.setRuleParams}

View file

@ -8,21 +8,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { i18n as i18nFormatter } from '@kbn/i18n';
import { AppMountParameters, CoreStart } from '@kbn/core/public';
import { AppMountParameters } from '@kbn/core-application-browser';
import { kibanaService } from '../../utils/kibana_service';
import { SyntheticsAppProps } from './contexts';
import { getIntegratedAppAvailability } from './utils/adapters';
import { DEFAULT_TIMEPICKER_QUICK_RANGES, INTEGRATED_SOLUTIONS } from '../../../common/constants';
import { SyntheticsApp } from './synthetics_app';
import { ClientPluginsSetup, ClientPluginsStart } from '../../plugin';
export function renderApp(
core: CoreStart,
plugins: ClientPluginsSetup,
startPlugins: ClientPluginsStart,
appMountParameters: AppMountParameters,
isDev: boolean,
isServerless: boolean
) {
export const getSyntheticsAppProps = (): SyntheticsAppProps => {
const { isDev, isServerless, coreStart, startPlugins, setupPlugins, appMountParameters } =
kibanaService;
const {
application: { capabilities },
chrome: { setBadge, setHelpExtension },
@ -30,7 +26,7 @@ export function renderApp(
http: { basePath },
i18n,
theme,
} = core;
} = kibanaService.coreStart;
const { apm, infrastructure, logs } = getIntegratedAppAvailability(
capabilities,
@ -40,24 +36,22 @@ export function renderApp(
const canSave = (capabilities.uptime.save ?? false) as boolean; // TODO: Determine for synthetics
const darkMode = theme.getTheme().darkMode;
const props: SyntheticsAppProps = {
return {
isDev,
plugins,
setupPlugins,
canSave,
core,
coreStart,
i18n,
startPlugins,
basePath: basePath.get(),
darkMode,
commonlyUsedRanges: core.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES),
commonlyUsedRanges: coreStart.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES),
isApmAvailable: apm,
isInfraAvailable: infrastructure,
isLogsAvailable: logs,
renderGlobalHelpControls: () =>
setHelpExtension({
appName: i18nFormatter.translate('xpack.synthetics.header.appName', {
defaultMessage: 'Synthetics',
}),
appName: SYNTHETICS_APP_NAME,
links: [
{
linkType: 'documentation',
@ -72,13 +66,21 @@ export function renderApp(
setBadge,
appMountParameters,
isServerless,
setBreadcrumbs: startPlugins.serverless?.setBreadcrumbs ?? core.chrome.setBreadcrumbs,
setBreadcrumbs: startPlugins.serverless?.setBreadcrumbs ?? coreStart.chrome.setBreadcrumbs,
};
};
export function renderApp(appMountParameters: AppMountParameters) {
const props: SyntheticsAppProps = getSyntheticsAppProps();
ReactDOM.render(<SyntheticsApp {...props} />, appMountParameters.element);
return () => {
startPlugins.data.search.session.clear();
props.startPlugins.data.search.session.clear();
ReactDOM.unmountComponentAtNode(appMountParameters.element);
};
}
const SYNTHETICS_APP_NAME = i18nFormatter.translate('xpack.synthetics.header.appName', {
defaultMessage: 'Synthetics',
});

View file

@ -11,5 +11,5 @@ import { toMountPoint } from '@kbn/react-kibana-mount';
import { kibanaService } from '../../../../utils/kibana_service';
export function toastTitle({ title, testAttribute }: { title: string; testAttribute?: string }) {
return toMountPoint(<p data-test-sub={testAttribute}>{title}</p>, kibanaService.core);
return toMountPoint(<p data-test-sub={testAttribute}>{title}</p>, kibanaService.coreStart);
}

View file

@ -9,7 +9,11 @@ import { createReducer } from '@reduxjs/toolkit';
import { OverviewStatusState } from '../../../../../common/runtime_types';
import { IHttpSerializedFetchError } from '..';
import { clearOverviewStatusErrorAction, fetchOverviewStatusAction } from './actions';
import {
clearOverviewStatusErrorAction,
fetchOverviewStatusAction,
quietFetchOverviewStatusAction,
} from './actions';
export interface OverviewStatusStateReducer {
loading: boolean;
@ -29,6 +33,10 @@ export const overviewStatusReducer = createReducer(initialState, (builder) => {
builder
.addCase(fetchOverviewStatusAction.get, (state) => {
state.status = null;
state.loading = true;
})
.addCase(quietFetchOverviewStatusAction.get, (state) => {
state.loading = true;
})
.addCase(fetchOverviewStatusAction.success, (state, action) => {
state.status = {
@ -36,9 +44,11 @@ export const overviewStatusReducer = createReducer(initialState, (builder) => {
allConfigs: { ...action.payload.upConfigs, ...action.payload.downConfigs },
};
state.loaded = true;
state.loading = false;
})
.addCase(fetchOverviewStatusAction.fail, (state, action) => {
state.error = action.payload;
state.loading = false;
})
.addCase(clearOverviewStatusErrorAction, (state) => {
state.error = null;

View file

@ -8,5 +8,5 @@
import { SyntheticsAppState } from '../root_reducer';
export const selectOverviewStatus = ({
overviewStatus: { status, error, loaded },
}: SyntheticsAppState) => ({ status, error, loaded });
overviewStatus: { status, error, loaded, loading },
}: SyntheticsAppState) => ({ status, error, loaded, loading });

View file

@ -81,13 +81,13 @@ export function* setDynamicSettingsEffect() {
yield call(setDynamicSettings, { settings: action.payload });
yield put(updateDefaultAlertingAction.get());
yield put(setDynamicSettingsAction.success(action.payload));
kibanaService.core.notifications.toasts.addSuccess(
kibanaService.coreSetup.notifications.toasts.addSuccess(
i18n.translate('xpack.synthetics.settings.saveSuccess', {
defaultMessage: 'Settings saved!',
})
);
} catch (err) {
kibanaService.core.notifications.toasts.addError(err, {
kibanaService.coreSetup.notifications.toasts.addError(err, {
title: couldNotSaveSettingsText,
});
yield put(setDynamicSettingsAction.fail(err));

View file

@ -67,7 +67,7 @@ export function fetchEffectFactory<T, R, S, F>(
if (typeof onFailure === 'function') {
onFailure?.(error);
} else if (typeof onFailure === 'string') {
kibanaService.core.notifications.toasts.addError(
kibanaService.coreSetup.notifications.toasts.addError(
{ ...error, message: serializedError.body?.message ?? error.message },
{
title: onFailure,
@ -104,7 +104,7 @@ export function fetchEffectFactory<T, R, S, F>(
if (typeof onSuccess === 'function') {
onSuccess(response as R);
} else if (onSuccess && typeof onSuccess === 'string') {
kibanaService.core.notifications.toasts.addSuccess(onSuccess);
kibanaService.coreSetup.notifications.toasts.addSuccess(onSuccess);
}
}
} catch (error) {

View file

@ -6,42 +6,30 @@
*/
import React, { useEffect } from 'react';
import { Provider as ReduxProvider } from 'react-redux';
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { InspectorContextProvider } from '@kbn/observability-shared-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { Router } from '@kbn/shared-ux-router';
import { SyntheticsSharedContext } from './contexts/synthetics_shared_context';
import { kibanaService } from '../../utils/kibana_service';
import { ActionMenu } from './components/common/header/action_menu';
import { TestNowModeFlyoutContainer } from './components/test_now_mode/test_now_mode_flyout_container';
import {
SyntheticsAppProps,
SyntheticsRefreshContextProvider,
SyntheticsSettingsContextProvider,
SyntheticsStartupPluginsContextProvider,
SyntheticsThemeContextProvider,
} from './contexts';
import { SyntheticsDataViewContextProvider } from './contexts/synthetics_data_view_context';
import { SyntheticsAppProps, SyntheticsSettingsContextProvider } from './contexts';
import { PageRouter } from './routes';
import { setBasePath, storage, store } from './state';
import { setBasePath, store } from './state';
const Application = (props: SyntheticsAppProps) => {
const {
basePath,
canSave,
core,
darkMode,
plugins,
coreStart,
startPlugins,
renderGlobalHelpControls,
setBadge,
startPlugins,
appMountParameters,
} = props;
@ -62,16 +50,17 @@ const Application = (props: SyntheticsAppProps) => {
);
}, [canSave, renderGlobalHelpControls, setBadge]);
kibanaService.core = core;
kibanaService.startPlugins = startPlugins;
kibanaService.theme = props.appMountParameters.theme$;
store.dispatch(setBasePath(basePath));
const PresentationContextProvider =
startPlugins.presentationUtil?.ContextProvider ?? React.Fragment;
return (
<KibanaRenderContextProvider {...core}>
<KibanaRenderContextProvider {...coreStart}>
<KibanaThemeProvider
theme={core.theme}
theme={coreStart.theme}
modify={{
breakpoint: {
xxl: 1600,
@ -79,54 +68,21 @@ const Application = (props: SyntheticsAppProps) => {
},
}}
>
<ReduxProvider store={store}>
<KibanaContextProvider
services={{
...core,
...plugins,
storage,
data: startPlugins.data,
inspector: startPlugins.inspector,
triggersActionsUi: startPlugins.triggersActionsUi,
observability: startPlugins.observability,
observabilityShared: startPlugins.observabilityShared,
observabilityAIAssistant: startPlugins.observabilityAIAssistant,
exploratoryView: startPlugins.exploratoryView,
cases: startPlugins.cases,
spaces: startPlugins.spaces,
fleet: startPlugins.fleet,
unifiedSearch: startPlugins.unifiedSearch,
}}
>
<SyntheticsDataViewContextProvider dataViews={startPlugins.dataViews}>
<Router history={appMountParameters.history}>
<EuiThemeProvider darkMode={darkMode}>
<SyntheticsRefreshContextProvider>
<SyntheticsSettingsContextProvider {...props}>
<SyntheticsThemeContextProvider darkMode={darkMode}>
<SyntheticsStartupPluginsContextProvider {...startPlugins}>
<div className={APP_WRAPPER_CLASS} data-test-subj="syntheticsApp">
<RedirectAppLinks
coreStart={{
application: core.application,
}}
>
<InspectorContextProvider>
<PageRouter />
<ActionMenu appMountParameters={appMountParameters} />
<TestNowModeFlyoutContainer />
</InspectorContextProvider>
</RedirectAppLinks>
</div>
</SyntheticsStartupPluginsContextProvider>
</SyntheticsThemeContextProvider>
</SyntheticsSettingsContextProvider>
</SyntheticsRefreshContextProvider>
</EuiThemeProvider>
</Router>
</SyntheticsDataViewContextProvider>
</KibanaContextProvider>
</ReduxProvider>
<SyntheticsSharedContext {...props}>
<PresentationContextProvider>
<Router history={appMountParameters.history}>
<SyntheticsSettingsContextProvider {...props}>
<div className={APP_WRAPPER_CLASS} data-test-subj="syntheticsApp">
<InspectorContextProvider>
<PageRouter />
<ActionMenu appMountParameters={appMountParameters} />
<TestNowModeFlyoutContainer />
</InspectorContextProvider>
</div>
</SyntheticsSettingsContextProvider>
</Router>
</PresentationContextProvider>
</SyntheticsSharedContext>
</KibanaThemeProvider>
</KibanaRenderContextProvider>
);

View file

@ -34,10 +34,7 @@ import { MountWithReduxProvider } from './helper_with_redux';
import { AppState } from '../../state';
import { stringifyUrlParams } from '../url_params';
import { ClientPluginsStart } from '../../../../plugin';
import {
SyntheticsRefreshContextProvider,
SyntheticsStartupPluginsContextProvider,
} from '../../contexts';
import { SyntheticsRefreshContextProvider } from '../../contexts';
import { kibanaService } from '../../../../utils/kibana_service';
type DeepPartial<T> = {
@ -182,21 +179,14 @@ export function MockKibanaProvider<ExtraCore>({
}: MockKibanaProviderProps<ExtraCore>) {
const coreOptions = merge({}, mockCore(), core);
kibanaService.core = coreOptions as any;
kibanaService.coreStart = coreOptions as any;
return (
<KibanaContextProvider services={{ ...coreOptions }} {...kibanaProps}>
<SyntheticsRefreshContextProvider>
<SyntheticsStartupPluginsContextProvider
data={(coreOptions as any).data}
observability={(coreOptions as any).observability}
observabilityShared={(coreOptions as any).observabilityShared}
exploratoryView={(coreOptions as any).exploratoryView}
>
<EuiThemeProvider darkMode={false}>
<I18nProvider>{children}</I18nProvider>
</EuiThemeProvider>
</SyntheticsStartupPluginsContextProvider>
<EuiThemeProvider darkMode={false}>
<I18nProvider>{children}</I18nProvider>
</EuiThemeProvider>
</SyntheticsRefreshContextProvider>
</KibanaContextProvider>
);

View file

@ -25,7 +25,7 @@ import type {
ExploratoryViewPublicSetup,
ExploratoryViewPublicStart,
} from '@kbn/exploratory-view-plugin/public';
import { EmbeddableStart } from '@kbn/embeddable-plugin/public';
import { EmbeddableStart, EmbeddableSetup } from '@kbn/embeddable-plugin/public';
import {
TriggersAndActionsUIPublicPluginSetup,
TriggersAndActionsUIPublicPluginStart,
@ -50,12 +50,18 @@ import type {
ObservabilitySharedPluginSetup,
ObservabilitySharedPluginStart,
} from '@kbn/observability-shared-plugin/public';
import { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/public/plugin';
import {
ObservabilityAIAssistantPublicSetup,
ObservabilityAIAssistantPublicStart,
} from '@kbn/observability-ai-assistant-plugin/public';
import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
import type { UiActionsSetup } from '@kbn/ui-actions-plugin/public';
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
import { DashboardStart, DashboardSetup } from '@kbn/dashboard-plugin/public';
import { registerSyntheticsEmbeddables } from './apps/embeddables/register_embeddables';
import { kibanaService } from './utils/kibana_service';
import { PLUGIN } from '../common/constants/plugin';
import { OVERVIEW_ROUTE } from '../common/constants/ui';
import { locators } from './apps/locators';
@ -72,7 +78,10 @@ export interface ClientPluginsSetup {
share: SharePluginSetup;
triggersActionsUi: TriggersAndActionsUIPublicPluginSetup;
cloud?: CloudSetup;
embeddable: EmbeddableSetup;
serverless?: ServerlessPluginSetup;
uiActions: UiActionsSetup;
dashboard: DashboardSetup;
}
export interface ClientPluginsStart {
@ -102,6 +111,8 @@ export interface ClientPluginsStart {
usageCollection: UsageCollectionStart;
serverless: ServerlessPluginStart;
licenseManagement?: LicenseManagementUIPluginSetup;
presentationUtil: PresentationUtilPluginStart;
dashboard: DashboardStart;
}
export interface SyntheticsPluginServices extends Partial<CoreStart> {
@ -123,12 +134,25 @@ export class SyntheticsPlugin
this._packageInfo = initContext.env.packageInfo;
}
public setup(core: CoreSetup<ClientPluginsStart, unknown>, plugins: ClientPluginsSetup): void {
public setup(
coreSetup: CoreSetup<ClientPluginsStart, unknown>,
plugins: ClientPluginsSetup
): void {
locators.forEach((locator) => {
plugins.share.url.locators.create(locator);
});
registerSyntheticsRoutesWithNavigation(core, plugins);
registerSyntheticsRoutesWithNavigation(coreSetup, plugins);
coreSetup.getStartServices().then(([coreStart, clientPluginsStart]) => {
kibanaService.init({
coreSetup,
coreStart,
startPlugins: clientPluginsStart,
isDev: this.initContext.env.mode.dev,
isServerless: this._isServerless,
});
});
const appKeywords = [
'Synthetics',
@ -149,7 +173,7 @@ export class SyntheticsPlugin
];
// Register the Synthetics UI plugin
core.application.register({
coreSetup.application.register({
id: 'synthetics',
euiIconType: 'logoObservability',
order: 8400,
@ -177,23 +201,20 @@ export class SyntheticsPlugin
},
],
mount: async (params: AppMountParameters) => {
const [coreStart, corePlugins] = await core.getStartServices();
kibanaService.appMountParameters = params;
const { renderApp } = await import('./apps/synthetics/render_app');
return renderApp(
coreStart,
plugins,
corePlugins,
params,
this.initContext.env.mode.dev,
this._isServerless
);
await coreSetup.getStartServices();
return renderApp(params);
},
});
registerSyntheticsEmbeddables(coreSetup, plugins);
}
public start(coreStart: CoreStart, pluginsStart: ClientPluginsStart): void {
const { triggersActionsUi } = pluginsStart;
setStartServices(coreStart);
setStartServices(coreStart);

View file

@ -6,43 +6,46 @@
*/
import type { Observable } from 'rxjs';
import type { CoreStart, CoreTheme } from '@kbn/core/public';
import { ClientPluginsStart } from '../../plugin';
import type { CoreStart, CoreTheme, CoreSetup } from '@kbn/core/public';
import { AppMountParameters } from '@kbn/core/public';
import { ClientPluginsSetup, ClientPluginsStart } from '../../plugin';
import { apiService } from '../api_service/api_service';
class KibanaService {
private static instance: KibanaService;
private _core!: CoreStart;
private _startPlugins!: ClientPluginsStart;
private _theme!: Observable<CoreTheme>;
public coreStart!: CoreStart;
public coreSetup!: CoreSetup;
public theme!: Observable<CoreTheme>;
public setupPlugins!: ClientPluginsSetup;
public isDev!: boolean;
public isServerless!: boolean;
public appMountParameters!: AppMountParameters;
public startPlugins!: ClientPluginsStart;
public get core() {
return this._core;
}
public set core(coreStart: CoreStart) {
this._core = coreStart;
apiService.http = this._core.http;
}
public get startPlugins() {
return this._startPlugins;
}
public set startPlugins(startPlugins: ClientPluginsStart) {
this._startPlugins = startPlugins;
}
public get theme() {
return this._theme;
}
public set theme(coreTheme: Observable<CoreTheme>) {
this._theme = coreTheme;
public init({
coreSetup,
coreStart,
startPlugins,
isDev,
isServerless,
}: {
coreSetup: CoreSetup;
coreStart: CoreStart;
startPlugins: ClientPluginsStart;
isDev: boolean;
isServerless: boolean;
}) {
this.coreSetup = coreSetup;
this.coreStart = coreStart;
this.startPlugins = startPlugins;
this.theme = coreStart.uiSettings.get$('theme:darkMode');
apiService.http = coreStart.http;
this.isDev = isDev;
this.isServerless = isServerless;
}
public get toasts() {
return this._core.notifications.toasts;
return this.coreStart.notifications.toasts;
}
private constructor() {}

View file

@ -40,7 +40,6 @@
"@kbn/kibana-react-plugin",
"@kbn/i18n-react",
"@kbn/securitysolution-io-ts-utils",
"@kbn/ui-theme",
"@kbn/es-query",
"@kbn/stack-connectors-plugin",
"@kbn/rule-data-utils",
@ -90,7 +89,15 @@
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-context-theme",
"@kbn/search-types"
"@kbn/search-types",
"@kbn/core-lifecycle-browser",
"@kbn/ui-actions-browser",
"@kbn/presentation-publishing",
"@kbn/presentation-containers",
"@kbn/ui-actions-plugin",
"@kbn/presentation-util-plugin",
"@kbn/core-application-browser",
"@kbn/dashboard-plugin"
],
"exclude": ["target/**/*"]
}