Skip to content

Commit

Permalink
[EPM] EPM to new plugin, UI part (#56882)
Browse files Browse the repository at this point in the history
* Move EPM home / list view over to ingest-manager

* Use react-router-dom in epm section.

* WIP: add package detail view.

* Use correct route.

* Only import needed types to public

* Remove obsolete file.

* Import type correctly

* Revert "Remove obsolete file."

This reverts commit 4b06110.

* Routes are still needed, fix them.

* Import types correctly

* More type import fixes.

* update get categories hook

* remove no longer used getCategories function

* get list packages hook working

* delete routes.tsx, cleanup links

* add the usePackageInstall hook

* replace rest of api calls with use/send request

* remove tmp_routes

* bring over breadcrumbs

* remove comments and get styles working

* get ride side col loading

* temp type fix

* remove useCore

* add assets

* remove comment

* add public directory to legacy ingest_manager and update asset path

* Fix PackageInfo type. Use for API & UI vs a saved object type.

The `as PackageInfo` cast was required because the pipeline was typed as returning `Installed | NotInstalled` which are saved object response.

Updating that to PackageInfo allows the `as` to be removed but revealed an incompatibility between the `assets` properties of RegistryPackage and PackageInfo

```
Types of property 'assets' are incompatible.
  Type 'Record<"kibana", Record<KibanaAssetType, KibanaAssetParts[]>>' is missing the following properties from type 'string[]': length, pop, push, concat, and 28 more.
```

It seems the `RegistryPackage & PackageAdditions` didn't cause the PackageAdditions.assets to replace the RegistryPackage.assets property.

I changed the definition of PackageInfo to do what I initially thought it was doing. See comments in models/epm.ts for more about how the new type is constructed.

* remove comment

* fix paths

* fix public paths

* fix path

* remove ui types file

* fix types

Co-authored-by: Sandra Gonzales <neptunian@users.noreply.github.com>
Co-authored-by: John Schulz <github.com@jfsiii.org>
  • Loading branch information
3 people authored Feb 18, 2020
1 parent 6e00b53 commit ac05485
Show file tree
Hide file tree
Showing 62 changed files with 2,012 additions and 40 deletions.
2 changes: 2 additions & 0 deletions x-pack/legacy/plugins/ingest_manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { resolve } from 'path';
import {
savedObjectMappings,
OUTPUT_SAVED_OBJECT_TYPE,
Expand All @@ -19,6 +20,7 @@ import {
export function ingestManager(kibana: any) {
return new kibana.Plugin({
id: 'ingestManager',
publicDir: resolve(__dirname, '../../../plugins/ingest_manager/public'),
uiExports: {
savedObjectSchemas: {
[AGENT_CONFIG_SAVED_OBJECT_TYPE]: {
Expand Down
16 changes: 14 additions & 2 deletions x-pack/plugins/ingest_manager/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ export enum InstallationStatus {
installed = 'installed',
notInstalled = 'not_installed',
}
export enum InstallStatus {
installed = 'installed',
notInstalled = 'not_installed',
installing = 'installing',
uninstalling = 'uninstalling',
}

export type DetailViewPanelName = 'overview' | 'data-sources';
export type ServiceName = 'kibana' | 'elasticsearch';
export type AssetType = KibanaAssetType | ElasticsearchAssetType | AgentAssetType;

Expand Down Expand Up @@ -146,9 +153,14 @@ interface PackageAdditions {
// Managers public HTTP response types
export type PackageList = PackageListItem[];

export type PackageListItem = Installable<RegistrySearchResult & PackageAdditions>;
export type PackageListItem = Installable<RegistrySearchResult>;
export type PackagesGroupedByStatus = Record<InstallationStatus, PackageList>;
export type PackageInfo = Installable<RegistryPackage & PackageAdditions>;
export type PackageInfo = Installable<
// remove the properties we'll be altering/replacing from the base type
Omit<RegistryPackage, keyof PackageAdditions> &
// now add our replacement definitions
PackageAdditions
>;

export interface Installation extends SavedObjectAttributes {
installed: AssetReference[];
Expand Down
5 changes: 2 additions & 3 deletions x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
CategorySummaryList,
Installable,
RegistryPackage,
Installed,
NotInstalled,
PackageInfo,
} from '../models/epm';

export interface GetCategoriesResponse {
Expand Down Expand Up @@ -57,7 +56,7 @@ export const GetInfoRequestSchema = {
};

export interface GetInfoResponse {
response: Installed | NotInstalled;
response: PackageInfo;
success: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export {
} from '../../../../common';
export const BASE_PATH = `/app/${PLUGIN_ID}`;
export const EPM_PATH = '/epm';
export const EPM_DETAIL_VIEW_PATH = `${EPM_PATH}/detail/:pkgkey/:panel?`;
export const AGENT_CONFIG_PATH = '/configs';
export const AGENT_CONFIG_DETAILS_PATH = '/configs/';
export const FLEET_PATH = '/fleet';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { HttpFetchQuery } from 'kibana/public';
import { useRequest, sendRequest } from './use_request';
import { epmRouteService } from '../../services';
import {
GetCategoriesResponse,
GetPackagesResponse,
GetInfoResponse,
InstallPackageResponse,
DeletePackageResponse,
} from '../../types';

export const useGetCategories = () => {
return useRequest<GetCategoriesResponse>({
path: epmRouteService.getCategoriesPath(),
method: 'get',
});
};

export const useGetPackages = (query: HttpFetchQuery = {}) => {
return useRequest<GetPackagesResponse>({
path: epmRouteService.getListPath(),
method: 'get',
query,
});
};

export const sendGetPackageInfoByKey = (pkgKey: string) => {
return sendRequest<GetInfoResponse>({
path: epmRouteService.getInfoPath(pkgKey),
method: 'get',
});
};

export const sendGetFileByPath = (filePath: string) => {
return sendRequest<string>({
path: epmRouteService.getFilePath(filePath),
method: 'get',
});
};

export const sendInstallPackage = (pkgkey: string) => {
return sendRequest<InstallPackageResponse>({
path: epmRouteService.getInstallPath(pkgkey),
method: 'get',
});
};

export const sendRemovePackage = (pkgkey: string) => {
return sendRequest<DeletePackageResponse>({
path: epmRouteService.getRemovePath(pkgkey),
method: 'get',
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { setHttpClient, sendRequest, useRequest } from './use_request';
export * from './agent_config';
export * from './agents';
export * from './enrollment_api_keys';
export * from './epm';
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { EPM_PATH, FLEET_PATH, AGENT_CONFIG_PATH } from './constants';
import { DefaultLayout } from './layouts';
import { IngestManagerOverview, EPMApp, AgentConfigApp, FleetApp } from './sections';
import { CoreContext, DepsContext, ConfigContext, setHttpClient } from './hooks';
import { PackageInstallProvider } from './sections/epm/hooks';

const IngestManagerRoutes = ({ ...rest }) => (
<EuiErrorBoundary>
Expand Down Expand Up @@ -67,7 +68,9 @@ const IngestManagerApp = ({
<DepsContext.Provider value={{ setup: setupDeps, start: startDeps }}>
<ConfigContext.Provider value={config}>
<EuiThemeProvider darkMode={isDarkMode}>
<IngestManagerRoutes />
<PackageInstallProvider notifications={coreStart.notifications}>
<IngestManagerRoutes />
</PackageInstallProvider>
</EuiThemeProvider>
</ConfigContext.Provider>
</DepsContext.Provider>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 {
EuiFacetButton,
EuiFacetGroup,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiText,
EuiTextColor,
EuiTitle,
} from '@elastic/eui';
import React, { Fragment } from 'react';
import styled from 'styled-components';
import {
AssetsGroupedByServiceByType,
AssetTypeToParts,
KibanaAssetType,
entries,
} from '../../../types';
import {
AssetIcons,
AssetTitleMap,
DisplayedAssets,
ServiceIcons,
ServiceTitleMap,
} from '../constants';

export function AssetsFacetGroup({ assets }: { assets: AssetsGroupedByServiceByType }) {
const FirstHeaderRow = styled(EuiFlexGroup)`
padding: 0 0 ${props => props.theme.eui.paddingSizes.m} 0;
`;

const HeaderRow = styled(EuiFlexGroup)`
padding: ${props => props.theme.eui.paddingSizes.m} 0;
`;

const FacetGroup = styled(EuiFacetGroup)`
flex-grow: 0;
`;

return (
<Fragment>
{entries(assets).map(([service, typeToParts], index) => {
const Header = index === 0 ? FirstHeaderRow : HeaderRow;
// filter out assets we are not going to display
const filteredTypes: AssetTypeToParts = entries(typeToParts).reduce(
(acc: any, [asset, value]) => {
if (DisplayedAssets[service].includes(asset)) acc[asset] = value;
return acc;
},
{}
);
return (
<Fragment key={service}>
<Header gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type={ServiceIcons[service]} />
</EuiFlexItem>

<EuiFlexItem>
<EuiTitle key={service} size="xs">
<EuiText>
<h4>{ServiceTitleMap[service]} Assets</h4>
</EuiText>
</EuiTitle>
</EuiFlexItem>
</Header>

<FacetGroup>
{entries(filteredTypes).map(([_type, parts]) => {
const type = _type as KibanaAssetType;
// only kibana assets have icons
const iconType = type in AssetIcons && AssetIcons[type];
const iconNode = iconType ? <EuiIcon type={iconType} size="s" /> : '';
const FacetButton = styled(EuiFacetButton)`
padding: '${props => props.theme.eui.paddingSizes.xs} 0';
height: 'unset';
`;
return (
<FacetButton
key={type}
quantity={parts.length}
icon={iconNode}
// https://github.com/elastic/eui/issues/2216
buttonRef={() => {}}
>
<EuiTextColor color="subdued">{AssetTitleMap[type]}</EuiTextColor>
</FacetButton>
);
})}
</FacetGroup>
</Fragment>
);
})}
</Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -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 { EuiIcon, EuiPanel, IconType } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';

export function IconPanel({ iconType }: { iconType: IconType }) {
const Panel = styled(EuiPanel)`
/* 🤢🤷 https://www.styled-components.com/docs/faqs#how-can-i-override-styles-with-higher-specificity */
&&& {
position: absolute;
text-align: center;
vertical-align: middle;
padding: ${props => props.theme.eui.spacerSizes.xl};
svg {
height: ${props => props.theme.eui.euiKeyPadMenuSize};
width: ${props => props.theme.eui.euiKeyPadMenuSize};
}
}
`;

return (
<Panel>
<EuiIcon type={iconType} size="original" />
</Panel>
);
}
Original file line number Diff line number Diff line change
@@ -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 { EuiButtonEmpty } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';

export function NavButtonBack({ href, text }: { href: string; text: string }) {
const ButtonEmpty = styled(EuiButtonEmpty)`
margin-right: ${props => props.theme.eui.spacerSizes.xl};
`;
return (
<ButtonEmpty iconType="arrowLeft" size="xs" flush="left" href={href}>
{text}
</ButtonEmpty>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { EuiCard, EuiIcon, ICON_TYPES } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
import { PackageInfo, PackageListItem } from '../../../types';
import { useLinks } from '../hooks';

export interface BadgeProps {
showInstalledBadge?: boolean;
}

type PackageCardProps = (PackageListItem | PackageInfo) & BadgeProps;

// adding the `href` causes EuiCard to use a `a` instead of a `button`
// `a` tags use `euiLinkColor` which results in blueish Badge text
const Card = styled(EuiCard)`
color: inherit;
`;

export function PackageCard({
description,
name,
title,
version,
showInstalledBadge,
status,
}: PackageCardProps) {
const { toDetailView } = useLinks();
const url = toDetailView({ name, version });

// try to find a logo in EUI
// TODO: first try to find icon in `icons` property
const iconType = ICON_TYPES.find(key => key.toLowerCase() === `logo${name}`);
const optionalIcon = iconType ? <EuiIcon type={iconType} size="l" /> : undefined;

return (
<Card
betaBadgeLabel={showInstalledBadge && status === 'installed' ? 'Installed' : ''}
layout="horizontal"
title={title || ''}
description={description}
icon={optionalIcon}
href={url}
/>
);
}
Loading

0 comments on commit ac05485

Please sign in to comment.