Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Fleet] Support for showing an Integration Detail Custom (UI Extension) tab #83805

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c8c3c5d
Support for rendering a custom component in Integration Details
paul-tavares Nov 18, 2020
e2e51c9
Refactor Fleet app initialization contexts in order to support testin…
paul-tavares Nov 18, 2020
9d4afc5
Initial implementation of test rendering helper tool
paul-tavares Nov 19, 2020
82a475f
adjust order of contexts to match prior implementation
paul-tavares Nov 19, 2020
df94c92
more specific implementation around custom tab component
paul-tavares Nov 19, 2020
028e0e1
Plugin dependencies mocks + renaming + expose mocks for other plugins
paul-tavares Nov 19, 2020
8bba5d1
refactor Endpoint to use mock builder from ingest
paul-tavares Nov 19, 2020
d6e106c
Merge remote-tracking branch 'upstream/master' into task/fleet-82485-…
paul-tavares Nov 23, 2020
88784fb
Fix plugin.ts after merge from master
paul-tavares Nov 23, 2020
4bd5c75
Adjustment to test renderer to use values from returned object during…
paul-tavares Nov 23, 2020
60a7ff3
More fixes after merge from master
paul-tavares Nov 23, 2020
333c10e
Additional fixes after master merge (jen's changes)
paul-tavares Nov 23, 2020
80a5e96
Initial test file for Integration details
paul-tavares Nov 23, 2020
94f0c01
Merge remote-tracking branch 'upstream/master' into task/fleet-82492-…
paul-tavares Nov 24, 2020
e6e8eb9
apply feedback from prior code review
paul-tavares Nov 24, 2020
d013587
Refactor FleetStartServices mock
paul-tavares Nov 24, 2020
b29029a
Fix TestRender's history instance due to use of Hash Router
paul-tavares Nov 24, 2020
4b31a39
Merge remote-tracking branch 'upstream/master' into task/fleet-82492-…
paul-tavares Nov 24, 2020
789ad25
Fixed some left over types
paul-tavares Nov 24, 2020
ae81b29
Fix eslint issue
paul-tavares Nov 25, 2020
495fbbd
Merge remote-tracking branch 'upstream/master' into task/fleet-82492-…
paul-tavares Nov 25, 2020
8fba859
remove ts-ignore
paul-tavares Nov 25, 2020
b3ddc2f
some more tests
paul-tavares Nov 25, 2020
093fecb
Complete test cases for custom tab in integrations
paul-tavares Nov 25, 2020
630f7c2
Merge remote-tracking branch 'upstream/master' into task/fleet-82492-…
paul-tavares Nov 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type InstallSource = 'registry' | 'upload';

export type EpmPackageInstallStatus = 'installed' | 'installing';

export type DetailViewPanelName = 'overview' | 'policies' | 'settings';
export type DetailViewPanelName = 'overview' | 'policies' | 'settings' | 'custom';
export type ServiceName = 'kibana' | 'elasticsearch';
export type AgentAssetType = typeof agentAssetTypes;
export type AssetType = KibanaAssetType | ElasticsearchAssetType | ValueOf<AgentAssetType>;
Expand Down
261 changes: 261 additions & 0 deletions x-pack/plugins/fleet/public/applications/fleet/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/*
* 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, { memo, useEffect, useState } from 'react';
import { AppMountParameters } from 'kibana/public';
import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel } from '@elastic/eui';
import { createHashHistory, History } from 'history';
import { Router, Redirect, Route, Switch } from 'react-router-dom';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the subtle change from using HashRouter to using Router along with History's createHashHistory. This is so that it can support testing.

import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import useObservable from 'react-use/lib/useObservable';
import {
ConfigContext,
FleetStatusProvider,
KibanaVersionContext,
sendGetPermissionsCheck,
sendSetup,
useBreadcrumbs,
useConfig,
} from './hooks';
import { Error, Loading } from './components';
import { IntraAppStateProvider } from './hooks/use_intra_app_state';
import { PackageInstallProvider } from './sections/epm/hooks';
import { PAGE_ROUTING_PATHS } from './constants';
import { DefaultLayout, WithoutHeaderLayout } from './layouts';
import { EPMApp } from './sections/epm';
import { AgentPolicyApp } from './sections/agent_policy';
import { DataStreamApp } from './sections/data_stream';
import { FleetApp } from './sections/agents';
import { IngestManagerOverview } from './sections/overview';
import { ProtectedRoute } from './index';
import { FleetConfigType, FleetStartServices } from '../../plugin';
import { UIExtensionsStorage } from './types';
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
import { EuiThemeProvider } from '../../../../xpack_legacy/common';
import { UIExtensionsContext } from './hooks/use_ui_extension';

const ErrorLayout = ({ children }: { children: JSX.Element }) => (
<EuiErrorBoundary>
<DefaultLayout showSettings={false}>
<WithoutHeaderLayout>{children}</WithoutHeaderLayout>
</DefaultLayout>
</EuiErrorBoundary>
);

const Panel = styled(EuiPanel)`
max-width: 500px;
margin-right: auto;
margin-left: auto;
`;

export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
useBreadcrumbs('base');

const [isPermissionsLoading, setIsPermissionsLoading] = useState<boolean>(false);
const [permissionsError, setPermissionsError] = useState<string>();
const [isInitialized, setIsInitialized] = useState(false);
const [initializationError, setInitializationError] = useState<Error | null>(null);

useEffect(() => {
(async () => {
setIsPermissionsLoading(false);
setPermissionsError(undefined);
setIsInitialized(false);
setInitializationError(null);
try {
setIsPermissionsLoading(true);
const permissionsResponse = await sendGetPermissionsCheck();
setIsPermissionsLoading(false);
if (permissionsResponse.data?.success) {
try {
const setupResponse = await sendSetup();
if (setupResponse.error) {
setInitializationError(setupResponse.error);
}
} catch (err) {
setInitializationError(err);
}
setIsInitialized(true);
} else {
setPermissionsError(permissionsResponse.data?.error || 'REQUEST_ERROR');
}
} catch (err) {
setPermissionsError('REQUEST_ERROR');
}
})();
}, []);

if (isPermissionsLoading || permissionsError) {
return (
<ErrorLayout>
{isPermissionsLoading ? (
<Loading />
) : permissionsError === 'REQUEST_ERROR' ? (
<Error
title={
<FormattedMessage
id="xpack.fleet.permissionsRequestErrorMessageTitle"
defaultMessage="Unable to check permissions"
/>
}
error={i18n.translate('xpack.fleet.permissionsRequestErrorMessageDescription', {
defaultMessage: 'There was a problem checking Fleet permissions',
})}
/>
) : (
<Panel>
<EuiEmptyPrompt
iconType="securityApp"
title={
<h2>
{permissionsError === 'MISSING_SUPERUSER_ROLE' ? (
<FormattedMessage
id="xpack.fleet.permissionDeniedErrorTitle"
defaultMessage="Permission denied"
/>
) : (
<FormattedMessage
id="xpack.fleet.securityRequiredErrorTitle"
defaultMessage="Security is not enabled"
/>
)}
</h2>
}
body={
<p>
{permissionsError === 'MISSING_SUPERUSER_ROLE' ? (
<FormattedMessage
id="xpack.fleet.permissionDeniedErrorMessage"
defaultMessage="You are not authorized to access Fleet. Fleet requires {roleName} privileges."
values={{ roleName: <EuiCode>superuser</EuiCode> }}
/>
) : (
<FormattedMessage
id="xpack.fleet.securityRequiredErrorMessage"
defaultMessage="You must enable security in Kibana and Elasticsearch to use Fleet."
/>
)}
</p>
}
/>
</Panel>
)}
</ErrorLayout>
);
}

if (!isInitialized || initializationError) {
return (
<ErrorLayout>
{initializationError ? (
<Error
title={
<FormattedMessage
id="xpack.fleet.initializationErrorMessageTitle"
defaultMessage="Unable to initialize Fleet"
/>
}
error={initializationError}
/>
) : (
<Loading />
)}
</ErrorLayout>
);
}

return <>{children}</>;
});

/**
* Fleet Application context all the way down to the Router, but with no permissions or setup checks
* and no routes defined
*/
export const FleetAppContext: React.FC<{
basepath: string;
startServices: FleetStartServices;
config: FleetConfigType;
history: AppMountParameters['history'];
kibanaVersion: string;
extensions: UIExtensionsStorage;
/** For testing purposes only */
routerHistory?: History<any>;
}> = memo(
({
children,
startServices,
config,
history,
kibanaVersion,
extensions,
routerHistory = createHashHistory(),
}) => {
const isDarkMode = useObservable<boolean>(startServices.uiSettings.get$('theme:darkMode'));

return (
<startServices.i18n.Context>
<KibanaContextProvider services={{ ...startServices }}>
<EuiErrorBoundary>
<ConfigContext.Provider value={config}>
<KibanaVersionContext.Provider value={kibanaVersion}>
<EuiThemeProvider darkMode={isDarkMode}>
<UIExtensionsContext.Provider value={extensions}>
<FleetStatusProvider>
<IntraAppStateProvider kibanaScopedHistory={history}>
<Router history={routerHistory}>
<PackageInstallProvider notifications={startServices.notifications}>
{children}
</PackageInstallProvider>
</Router>
</IntraAppStateProvider>
</FleetStatusProvider>
</UIExtensionsContext.Provider>
</EuiThemeProvider>
</KibanaVersionContext.Provider>
</ConfigContext.Provider>
</EuiErrorBoundary>
</KibanaContextProvider>
</startServices.i18n.Context>
);
}
);

export const AppRoutes = memo(() => {
const { agents } = useConfig();

return (
<Switch>
<Route path={PAGE_ROUTING_PATHS.integrations}>
<DefaultLayout section="epm">
<EPMApp />
</DefaultLayout>
</Route>
<Route path={PAGE_ROUTING_PATHS.policies}>
<DefaultLayout section="agent_policy">
<AgentPolicyApp />
</DefaultLayout>
</Route>
<Route path={PAGE_ROUTING_PATHS.data_streams}>
<DefaultLayout section="data_stream">
<DataStreamApp />
</DefaultLayout>
</Route>
<ProtectedRoute path={PAGE_ROUTING_PATHS.fleet} isAllowed={agents.enabled}>
<DefaultLayout section="fleet">
<FleetApp />
</DefaultLayout>
</ProtectedRoute>
<Route exact path={PAGE_ROUTING_PATHS.overview}>
<DefaultLayout section="overview">
<IngestManagerOverview />
</DefaultLayout>
</Route>
<Redirect to="/" />
</Switch>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,13 @@ const paginationFromUrlParams = (urlParams: UrlPaginationParams): Pagination =>
// Search params can appear multiple times in the URL, in which case the value for them,
// once parsed, would be an array. In these case, we take the last value defined
pagination.currentPage = Number(
(Array.isArray(urlParams.currentPage)
? urlParams.currentPage[urlParams.currentPage.length - 1]
: urlParams.currentPage) ?? pagination.currentPage
(Array.isArray(urlParams.currentPage) ? urlParams.currentPage.pop() : urlParams.currentPage) ??
pagination.currentPage
);
pagination.pageSize =
Number(
(Array.isArray(urlParams.pageSize)
? urlParams.pageSize[urlParams.pageSize.length - 1]
: urlParams.pageSize) ?? pagination.pageSize
(Array.isArray(urlParams.pageSize) ? urlParams.pageSize.pop() : urlParams.pageSize) ??
pagination.pageSize
) ?? pagination.pageSize;

// If Current Page is not a valid positive integer, set it to 1
Expand Down
Loading