diff --git a/x-pack/legacy/plugins/ingest_manager/index.ts b/x-pack/legacy/plugins/ingest_manager/index.ts index 766dbbfd288bc..47c6478f66471 100644 --- a/x-pack/legacy/plugins/ingest_manager/index.ts +++ b/x-pack/legacy/plugins/ingest_manager/index.ts @@ -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, @@ -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]: { diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts index 89983ccb2998b..f664bc3fae06a 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -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; @@ -146,9 +153,14 @@ interface PackageAdditions { // Managers public HTTP response types export type PackageList = PackageListItem[]; -export type PackageListItem = Installable; +export type PackageListItem = Installable; export type PackagesGroupedByStatus = Record; -export type PackageInfo = Installable; +export type PackageInfo = Installable< + // remove the properties we'll be altering/replacing from the base type + Omit & + // now add our replacement definitions + PackageAdditions +>; export interface Installation extends SavedObjectAttributes { installed: AssetReference[]; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts index b92ebe69b4882..2233ef1f53a9f 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/epm.ts @@ -9,8 +9,7 @@ import { CategorySummaryList, Installable, RegistryPackage, - Installed, - NotInstalled, + PackageInfo, } from '../models/epm'; export interface GetCategoriesResponse { @@ -57,7 +56,7 @@ export const GetInfoRequestSchema = { }; export interface GetInfoResponse { - response: Installed | NotInstalled; + response: PackageInfo; success: boolean; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts index d9db7416c3b5c..1e7817843e973 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts @@ -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'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/epm.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/epm.ts new file mode 100644 index 0000000000000..d213a318705b4 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/epm.ts @@ -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({ + path: epmRouteService.getCategoriesPath(), + method: 'get', + }); +}; + +export const useGetPackages = (query: HttpFetchQuery = {}) => { + return useRequest({ + path: epmRouteService.getListPath(), + method: 'get', + query, + }); +}; + +export const sendGetPackageInfoByKey = (pkgKey: string) => { + return sendRequest({ + path: epmRouteService.getInfoPath(pkgKey), + method: 'get', + }); +}; + +export const sendGetFileByPath = (filePath: string) => { + return sendRequest({ + path: epmRouteService.getFilePath(filePath), + method: 'get', + }); +}; + +export const sendInstallPackage = (pkgkey: string) => { + return sendRequest({ + path: epmRouteService.getInstallPath(pkgkey), + method: 'get', + }); +}; + +export const sendRemovePackage = (pkgkey: string) => { + return sendRequest({ + path: epmRouteService.getRemovePath(pkgkey), + method: 'get', + }); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts index 3cc5328fdd652..4740e6a15c54f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts @@ -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'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx index 2c6adc577424d..ae44484ca1697 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx @@ -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 }) => ( @@ -67,7 +68,9 @@ const IngestManagerApp = ({ - + + + diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_kibana_getting_started@2x.png b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_kibana_getting_started@2x.png new file mode 100644 index 0000000000000..cad64be0b6e36 Binary files /dev/null and b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/assets/illustration_kibana_getting_started@2x.png differ diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx new file mode 100644 index 0000000000000..219896dd27ef7 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/assets_facet_group.tsx @@ -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 ( + + {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 ( + +
+ + + + + + + +

{ServiceTitleMap[service]} Assets

+
+
+
+
+ + + {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 ? : ''; + const FacetButton = styled(EuiFacetButton)` + padding: '${props => props.theme.eui.paddingSizes.xs} 0'; + height: 'unset'; + `; + return ( + {}} + > + {AssetTitleMap[type]} + + ); + })} + +
+ ); + })} +
+ ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icon_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icon_panel.tsx new file mode 100644 index 0000000000000..7ce386ed56f5f --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/icon_panel.tsx @@ -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 ( + + + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/nav_button_back.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/nav_button_back.tsx new file mode 100644 index 0000000000000..0c01bb72b339a --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/nav_button_back.tsx @@ -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 ( + + {text} + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx new file mode 100644 index 0000000000000..d622fd2cbf84e --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_card.tsx @@ -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 ? : undefined; + + return ( + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx new file mode 100644 index 0000000000000..34e1763c44255 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/package_list_grid.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import React, { Fragment, ReactNode } from 'react'; +import { PackageList } from '../../../types'; +import { BadgeProps, PackageCard } from './package_card'; + +type ListProps = { + controls?: ReactNode; + title: string; + list: PackageList; +} & BadgeProps; + +export function PackageListGrid({ controls, title, list, showInstalledBadge }: ListProps) { + const controlsContent = ; + const gridContent = ; + + return ( + + {controlsContent} + {gridContent} + + ); +} + +interface ControlsColumnProps { + controls: ReactNode; + title: string; +} + +function ControlsColumn({ controls, title }: ControlsColumnProps) { + return ( + + +

{title}

+
+ + + {controls} + + +
+ ); +} + +type GridColumnProps = { + list: PackageList; +} & BadgeProps; + +function GridColumn({ list }: GridColumnProps) { + return ( + + {list.map(item => ( + + + + ))} + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/requirements.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/requirements.tsx new file mode 100644 index 0000000000000..f60d2d83ed45e --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/requirements.tsx @@ -0,0 +1,57 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTextColor, EuiTitle } from '@elastic/eui'; +import React, { Fragment } from 'react'; +import styled from 'styled-components'; +import { RequirementsByServiceName, entries } from '../../../types'; +import { ServiceTitleMap } from '../constants'; +import { Version } from './version'; + +export interface RequirementsProps { + requirements: RequirementsByServiceName; +} + +const FlexGroup = styled(EuiFlexGroup)` + padding: 0 0 ${props => props.theme.eui.paddingSizes.m} 0; + margin: 0; +`; +const StyledVersion = styled(Version)` + font-size: ${props => props.theme.eui.euiFontSizeXS}; +`; + +export function Requirements(props: RequirementsProps) { + const { requirements } = props; + + return ( + + + + + + + + +

Elastic Stack Compatibility

+
+
+
+
+ {entries(requirements).map(([service, requirement]) => ( + + + + {ServiceTitleMap[service]}: + + + + + + + ))} +
+ ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/version.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/version.tsx new file mode 100644 index 0000000000000..537f6201dea06 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/components/version.tsx @@ -0,0 +1,22 @@ +/* + * 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 from 'react'; +import styled from 'styled-components'; +import { RequirementVersion } from '../../../types'; + +const CodeText = styled.span` + font-family: ${props => props.theme.eui.euiCodeFontFamily}; +`; +export function Version({ + className, + version, +}: { + className?: string; + version: RequirementVersion; +}) { + return {version}; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx new file mode 100644 index 0000000000000..3a6dfe4a87daf --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IconType } from '@elastic/eui'; +import { AssetType, ElasticsearchAssetType, KibanaAssetType, ServiceName } from '../../types'; + +// only allow Kibana assets for the kibana key, ES asssets for elasticsearch, etc +type ServiceNameToAssetTypes = Record, KibanaAssetType[]> & + Record, ElasticsearchAssetType[]>; + +export const DisplayedAssets: ServiceNameToAssetTypes = { + kibana: Object.values(KibanaAssetType), + elasticsearch: Object.values(ElasticsearchAssetType), +}; + +export const AssetTitleMap: Record = { + dashboard: 'Dashboard', + 'ilm-policy': 'ILM Policy', + 'ingest-pipeline': 'Ingest Pipeline', + 'index-pattern': 'Index Pattern', + 'index-template': 'Index Template', + search: 'Saved Search', + visualization: 'Visualization', + input: 'Agent input', +}; + +export const ServiceTitleMap: Record = { + elasticsearch: 'Elasticsearch', + kibana: 'Kibana', +}; + +export const AssetIcons: Record = { + dashboard: 'dashboardApp', + 'index-pattern': 'indexPatternApp', + search: 'searchProfilerApp', + visualization: 'visualizeApp', +}; + +export const ServiceIcons: Record = { + elasticsearch: 'logoElasticsearch', + kibana: 'logoKibana', +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx new file mode 100644 index 0000000000000..589ce5f5dbd25 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/index.tsx @@ -0,0 +1,15 @@ +/* + * 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 { useBreadcrumbs } from './use_breadcrumbs'; +export { useLinks } from './use_links'; +export { + PackageInstallProvider, + useDeletePackage, + useGetPackageInstallStatus, + useInstallPackage, + useSetPackageInstallStatus, +} from './use_package_install'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_breadcrumbs.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_breadcrumbs.tsx new file mode 100644 index 0000000000000..6222d346432c3 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_breadcrumbs.tsx @@ -0,0 +1,13 @@ +/* + * 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 { ChromeBreadcrumb } from '../../../../../../../../../src/core/public'; +import { useCore } from '../../../hooks'; + +export function useBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]) { + const { chrome } = useCore(); + return chrome.setBreadcrumbs(newBreadcrumbs); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx new file mode 100644 index 0000000000000..d4ed3624a6e68 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_links.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { generatePath } from 'react-router-dom'; +import { useCore } from '../../../hooks/use_core'; +import { PLUGIN_ID } from '../../../constants'; +import { epmRouteService } from '../../../services'; +import { DetailViewPanelName } from '../../../types'; +import { BASE_PATH, EPM_PATH, EPM_DETAIL_VIEW_PATH } from '../../../constants'; + +// TODO: get this from server/packages/handlers.ts (move elsewhere?) +// seems like part of the name@version change +interface DetailParams { + name: string; + version: string; + panel?: DetailViewPanelName; + withAppRoot?: boolean; +} + +const removeRelativePath = (relativePath: string): string => + new URL(relativePath, 'http://example.com').pathname; + +export function useLinks() { + const { http } = useCore(); + function appRoot(path: string) { + // include '#' because we're using HashRouter + return http.basePath.prepend(BASE_PATH + '#' + path); + } + + return { + toAssets: (path: string) => + http.basePath.prepend( + `/plugins/${PLUGIN_ID}/applications/ingest_manager/sections/epm/assets/${path}` + ), + toImage: (path: string) => http.basePath.prepend(epmRouteService.getFilePath(path)), + toRelativeImage: ({ + path, + packageName, + version, + }: { + path: string; + packageName: string; + version: string; + }) => { + const imagePath = removeRelativePath(path); + const pkgkey = `${packageName}-${version}`; + const filePath = `${epmRouteService.getInfoPath(pkgkey)}/${imagePath}`; + return http.basePath.prepend(filePath); + }, + toListView: () => appRoot(EPM_PATH), + toDetailView: ({ name, version, panel, withAppRoot = true }: DetailParams) => { + // panel is optional, but `generatePath` won't accept `path: undefined` + // so use this to pass `{ pkgkey }` or `{ pkgkey, panel }` + const params = Object.assign({ pkgkey: `${name}-${version}` }, panel ? { panel } : {}); + const path = generatePath(EPM_DETAIL_VIEW_PATH, params); + return withAppRoot ? appRoot(path) : path; + }, + }; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx new file mode 100644 index 0000000000000..f737a258f5331 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/hooks/use_package_install.tsx @@ -0,0 +1,142 @@ +/* + * 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 createContainer from 'constate'; +import React, { useCallback, useState } from 'react'; +import { NotificationsStart } from 'src/core/public'; +import { useLinks } from '.'; +import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { PackageInfo } from '../../../types'; +import { sendInstallPackage, sendRemovePackage } from '../../../hooks'; +import { InstallStatus } from '../../../types'; + +interface PackagesInstall { + [key: string]: PackageInstallItem; +} + +interface PackageInstallItem { + status: InstallStatus; +} + +type InstallPackageProps = Pick; + +function usePackageInstall({ notifications }: { notifications: NotificationsStart }) { + const [packages, setPackage] = useState({}); + const { toDetailView } = useLinks(); + + const setPackageInstallStatus = useCallback( + ({ name, status }: { name: PackageInfo['name']; status: InstallStatus }) => { + setPackage((prev: PackagesInstall) => ({ + ...prev, + [name]: { status }, + })); + }, + [] + ); + + const installPackage = useCallback( + async ({ name, version, title }: InstallPackageProps) => { + setPackageInstallStatus({ name, status: InstallStatus.installing }); + const pkgkey = `${name}-${version}`; + + try { + await sendInstallPackage(pkgkey); + setPackageInstallStatus({ name, status: InstallStatus.installed }); + const SuccessMsg =

Successfully installed {name}

; + + notifications.toasts.addSuccess({ + title: `Installed ${title} package`, + text: toMountPoint(SuccessMsg), + }); + + // TODO: this should probably live somewhere else and use , + // this hook could return the request state and a component could + // use that state. the component should be able to unsubscribe to prevent memory leaks + const packageUrl = toDetailView({ name, version }); + const dataSourcesUrl = toDetailView({ + name, + version, + panel: 'data-sources', + withAppRoot: false, + }); + if (window.location.href.includes(packageUrl)) window.location.hash = dataSourcesUrl; + } catch (err) { + setPackageInstallStatus({ name, status: InstallStatus.notInstalled }); + notifications.toasts.addWarning({ + title: `Failed to install ${title} package`, + text: + 'Something went wrong while trying to install this package. Please try again later.', + iconType: 'alert', + }); + } + }, + [notifications.toasts, setPackageInstallStatus, toDetailView] + ); + + const getPackageInstallStatus = useCallback( + (pkg: string): InstallStatus => { + return packages[pkg].status; + }, + [packages] + ); + + const deletePackage = useCallback( + async ({ name, version, title }: Pick) => { + setPackageInstallStatus({ name, status: InstallStatus.uninstalling }); + const pkgkey = `${name}-${version}`; + + try { + await sendRemovePackage(pkgkey); + setPackageInstallStatus({ name, status: InstallStatus.notInstalled }); + + const SuccessMsg =

Successfully deleted {title}

; + + notifications.toasts.addSuccess({ + title: `Deleted ${title} package`, + text: toMountPoint(SuccessMsg), + }); + + const packageUrl = toDetailView({ name, version }); + const dataSourcesUrl = toDetailView({ + name, + version, + panel: 'data-sources', + }); + if (window.location.href.includes(packageUrl)) window.location.href = dataSourcesUrl; + } catch (err) { + setPackageInstallStatus({ name, status: InstallStatus.installed }); + notifications.toasts.addWarning({ + title: `Failed to delete ${title} package`, + text: 'Something went wrong while trying to delete this package. Please try again later.', + iconType: 'alert', + }); + } + }, + [notifications.toasts, setPackageInstallStatus, toDetailView] + ); + + return { + packages, + installPackage, + setPackageInstallStatus, + getPackageInstallStatus, + deletePackage, + }; +} + +export const [ + PackageInstallProvider, + useInstallPackage, + useSetPackageInstallStatus, + useGetPackageInstallStatus, + useDeletePackage, +] = createContainer( + usePackageInstall, + value => value.installPackage, + value => value.setPackageInstallStatus, + value => value.getPackageInstallStatus, + value => value.deletePackage +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx index b0865cc2cfe5e..c1430cbe3a3ac 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/index.tsx @@ -5,5 +5,20 @@ */ import React from 'react'; +import { HashRouter as Router, Switch, Route } from 'react-router-dom'; -export const EPMApp: React.FC = () =>
hello world - epm app
; +import { Home } from './screens/home'; +import { Detail } from './screens/detail'; + +export const EPMApp: React.FC = () => ( + + + + + + + + + + +); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_delete.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_delete.tsx new file mode 100644 index 0000000000000..2b3be04ac476b --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_delete.tsx @@ -0,0 +1,32 @@ +/* + * 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 { EuiCallOut, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import React from 'react'; + +interface ConfirmPackageDeleteProps { + onCancel: () => void; + onConfirm: () => void; + packageName: string; + numOfAssets: number; +} +export const ConfirmPackageDelete = (props: ConfirmPackageDeleteProps) => { + const { onCancel, onConfirm, packageName, numOfAssets } = props; + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx new file mode 100644 index 0000000000000..137d9cf226b4d --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/confirm_package_install.tsx @@ -0,0 +1,36 @@ +/* + * 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 { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui'; +import React from 'react'; + +interface ConfirmPackageInstallProps { + onCancel: () => void; + onConfirm: () => void; + packageName: string; + numOfAssets: number; +} +export const ConfirmPackageInstall = (props: ConfirmPackageInstallProps) => { + const { onCancel, onConfirm, packageName, numOfAssets } = props; + return ( + + + + +

+ and will only be accessible to users who have permission to view this Space. Elasticsearch + assets are installed globally and will be accessible to all Kibana users. +

+
+
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx new file mode 100644 index 0000000000000..384cbbeed378e --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx @@ -0,0 +1,83 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { DEFAULT_PANEL, DetailParams } from '.'; +import { PackageInfo } from '../../../../types'; +import { AssetsFacetGroup } from '../../components/assets_facet_group'; +import { Requirements } from '../../components/requirements'; +import { CenterColumn, LeftColumn, RightColumn } from './layout'; +import { OverviewPanel } from './overview_panel'; +import { SideNavLinks } from './side_nav_links'; +import { DataSourcesPanel } from './data_sources_panel'; + +type ContentProps = PackageInfo & Pick & { hasIconPanel: boolean }; +export function Content(props: ContentProps) { + const { hasIconPanel, name, panel, version } = props; + const SideNavColumn = hasIconPanel + ? styled(LeftColumn)` + /* 🤢🤷 https://www.styled-components.com/docs/faqs#how-can-i-override-styles-with-higher-specificity */ + &&& { + margin-top: 77px; + } + ` + : LeftColumn; + + // fixes IE11 problem with nested flex items + const ContentFlexGroup = styled(EuiFlexGroup)` + flex: 0 0 auto !important; + `; + return ( + + + + + + + + + + + + ); +} + +type ContentPanelProps = PackageInfo & Pick; +export function ContentPanel(props: ContentPanelProps) { + const { panel, name, version } = props; + switch (panel) { + case 'data-sources': + return ; + case 'overview': + default: + return ; + } +} + +type RightColumnContentProps = PackageInfo & Pick; +function RightColumnContent(props: RightColumnContentProps) { + const { assets, requirement, panel } = props; + switch (panel) { + case 'overview': + return ( + + + + + + + + + + + + ); + default: + return ; + } +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content_collapse.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content_collapse.tsx new file mode 100644 index 0000000000000..9d5614debb42b --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content_collapse.tsx @@ -0,0 +1,96 @@ +/* + * 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 { EuiButton, EuiButtonEmpty, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import React, { Fragment, useCallback, useLayoutEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; + +const BottomFade = styled.div` + width: 100%; + background: ${props => + `linear-gradient(${props.theme.eui.euiColorEmptyShade}00 0%, ${props.theme.eui.euiColorEmptyShade} 100%)`}; + margin-top: -${props => parseInt(props.theme.eui.spacerSizes.xl, 10) * 2}px; + height: ${props => parseInt(props.theme.eui.spacerSizes.xl, 10) * 2}px; + position: absolute; +`; +const ContentCollapseContainer = styled.div` + position: relative; +`; +const CollapseButtonContainer = styled.div` + display: inline-block; + background-color: ${props => props.theme.eui.euiColorEmptyShade}; + position: absolute; + left: 50%; + transform: translateX(-50%); + top: ${props => parseInt(props.theme.eui.euiButtonHeight, 10) / 2}px; +`; +const CollapseButtonTop = styled(EuiButtonEmpty)` + float: right; +`; + +const CollapseButton = ({ + open, + toggleCollapse, +}: { + open: boolean; + toggleCollapse: () => void; +}) => { + return ( +
+ + + + + {open ? 'Collapse' : 'Read more'} + + +
+ ); +}; + +export const ContentCollapse = ({ children }: { children: React.ReactNode }) => { + const [open, setOpen] = useState(false); + const [height, setHeight] = useState('auto'); + const [collapsible, setCollapsible] = useState(true); + const contentEl = useRef(null); + const collapsedHeight = 360; + + // if content is too small, don't collapse + useLayoutEffect( + () => + contentEl.current && contentEl.current.clientHeight < collapsedHeight + ? setCollapsible(false) + : setHeight(collapsedHeight), + [] + ); + + const clickOpen = useCallback(() => { + setOpen(!open); + }, [open]); + + return ( + + {collapsible ? ( + +
+ {open && ( + + Collapse + + )} + {children} +
+ {!open && } + +
+ ) : ( +
{children}
+ )} +
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/data_sources_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/data_sources_panel.tsx new file mode 100644 index 0000000000000..fa3245aec02c5 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/data_sources_panel.tsx @@ -0,0 +1,40 @@ +/* + * 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, { Fragment } from 'react'; +import { EuiTitle } from '@elastic/eui'; +import { Redirect } from 'react-router-dom'; +import { useLinks, useGetPackageInstallStatus } from '../../hooks'; +import { InstallStatus } from '../../../../types'; + +interface DataSourcesPanelProps { + name: string; + version: string; +} +export const DataSourcesPanel = ({ name, version }: DataSourcesPanelProps) => { + const { toDetailView } = useLinks(); + const getPackageInstallStatus = useGetPackageInstallStatus(); + const packageInstallStatus = getPackageInstallStatus(name); + // if they arrive at this page and the package is not installed, send them to overview + // this happens if they arrive with a direct url or they uninstall while on this tab + if (packageInstallStatus !== InstallStatus.installed) + return ( + + ); + return ( + + + Data Sources + + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx new file mode 100644 index 0000000000000..7646da06b3239 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/header.tsx @@ -0,0 +1,67 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiPage, EuiTitle, IconType } from '@elastic/eui'; +import React, { Fragment } from 'react'; +import styled from 'styled-components'; +import { PackageInfo } from '../../../../types'; +import { IconPanel } from '../../components/icon_panel'; +import { NavButtonBack } from '../../components/nav_button_back'; +import { Version } from '../../components/version'; +import { useLinks } from '../../hooks'; +import { InstallationButton } from './installation_button'; +import { CenterColumn, LeftColumn, RightColumn } from './layout'; + +const FullWidthNavRow = styled(EuiPage)` + /* no left padding so link is against column left edge */ + padding-left: 0; +`; + +const Text = styled.span` + margin-right: ${props => props.theme.eui.euiSizeM}; +`; + +const StyledVersion = styled(Version)` + font-size: ${props => props.theme.eui.euiFontSizeS}; + color: ${props => props.theme.eui.euiColorDarkShade}; +`; + +type HeaderProps = PackageInfo & { iconType?: IconType }; + +export function Header(props: HeaderProps) { + const { iconType, title, version } = props; + const { toListView } = useLinks(); + // useBreadcrumbs([{ text: PLUGIN.TITLE, href: toListView() }, { text: title }]); + + return ( + + + + + + {iconType ? ( + + + + ) : null} + + +

+ {title} + +

+
+
+ + + + + + + +
+
+ ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx new file mode 100644 index 0000000000000..4bc90c6a0f8fd --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/index.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiPage, EuiPageBody, EuiPageProps, ICON_TYPES } from '@elastic/eui'; +import React, { Fragment, useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import styled from 'styled-components'; +import { DetailViewPanelName, InstallStatus } from '../../../../types'; +import { PackageInfo } from '../../../../types'; +import { useSetPackageInstallStatus } from '../../hooks'; +import { Content } from './content'; +import { Header } from './header'; +import { sendGetPackageInfoByKey } from '../../../../hooks'; + +export const DEFAULT_PANEL: DetailViewPanelName = 'overview'; + +export interface DetailParams { + pkgkey: string; + panel?: DetailViewPanelName; +} + +export function Detail() { + // TODO: fix forced cast if possible + const { pkgkey, panel = DEFAULT_PANEL } = useParams() as DetailParams; + + const [info, setInfo] = useState(null); + const setPackageInstallStatus = useSetPackageInstallStatus(); + useEffect(() => { + sendGetPackageInfoByKey(pkgkey).then(response => { + const packageInfo = response.data?.response; + const title = packageInfo?.title; + const name = packageInfo?.name; + const status: InstallStatus = packageInfo?.status as any; + + // track install status state + if (name) { + setPackageInstallStatus({ name, status }); + } + if (packageInfo) { + setInfo({ ...packageInfo, title: title || '' }); + } + }); + }, [pkgkey, setPackageInstallStatus]); + + if (!info) return null; + + return ; +} + +const FullWidthHeader = styled(EuiPage)` + border-bottom: ${props => props.theme.eui.euiBorderThin}; + padding-bottom: ${props => props.theme.eui.paddingSizes.xl}; +`; + +const FullWidthContent = styled(EuiPage)` + background-color: ${props => props.theme.eui.euiColorEmptyShade}; + padding-top: ${props => parseInt(props.theme.eui.paddingSizes.xl, 10) * 1.25}px; + flex-grow: 1; +`; + +type LayoutProps = PackageInfo & Pick & Pick; +export function DetailLayout(props: LayoutProps) { + const { name, restrictWidth } = props; + const iconType = ICON_TYPES.find(key => key.toLowerCase() === `logo${name}`); + + return ( + + + +
+ + + + + + + + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx new file mode 100644 index 0000000000000..ba9ed9082f041 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/installation_button.tsx @@ -0,0 +1,95 @@ +/* + * 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 { EuiButton } from '@elastic/eui'; +import React, { Fragment, useCallback, useMemo, useState } from 'react'; +import { PackageInfo, InstallStatus } from '../../../../types'; +import { useDeletePackage, useGetPackageInstallStatus, useInstallPackage } from '../../hooks'; +import { ConfirmPackageDelete } from './confirm_package_delete'; +import { ConfirmPackageInstall } from './confirm_package_install'; + +interface InstallationButtonProps { + package: PackageInfo; +} + +export function InstallationButton(props: InstallationButtonProps) { + const { assets, name, title, version } = props.package; + const installPackage = useInstallPackage(); + const deletePackage = useDeletePackage(); + const getPackageInstallStatus = useGetPackageInstallStatus(); + const installationStatus = getPackageInstallStatus(name); + + const isInstalling = installationStatus === InstallStatus.installing; + const isRemoving = installationStatus === InstallStatus.uninstalling; + const isInstalled = installationStatus === InstallStatus.installed; + const [isModalVisible, setModalVisible] = useState(false); + const toggleModal = useCallback(() => { + setModalVisible(!isModalVisible); + }, [isModalVisible]); + + const handleClickInstall = useCallback(() => { + installPackage({ name, version, title }); + toggleModal(); + }, [installPackage, name, title, toggleModal, version]); + + const handleClickDelete = useCallback(() => { + deletePackage({ name, version, title }); + toggleModal(); + }, [deletePackage, name, title, toggleModal, version]); + + const numOfAssets = useMemo( + () => + Object.entries(assets).reduce( + (acc, [serviceName, serviceNameValue]) => + acc + + Object.entries(serviceNameValue).reduce( + (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, + 0 + ), + 0 + ), + [assets] + ); + + const installButton = ( + + {isInstalling ? 'Installing' : 'Install package'} + + ); + + const installedButton = ( + + {isInstalling ? 'Deleting' : 'Delete package'} + + ); + + const deletionModal = ( + + ); + + const installationModal = ( + + ); + + return ( + + {isInstalled ? installedButton : installButton} + {isModalVisible && (isInstalled ? deletionModal : installationModal)} + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/layout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/layout.tsx new file mode 100644 index 0000000000000..a802e35add7db --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/layout.tsx @@ -0,0 +1,37 @@ +/* + * 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 { EuiFlexItem } from '@elastic/eui'; +import React, { FunctionComponent, ReactNode } from 'react'; + +interface ColumnProps { + children?: ReactNode; + className?: string; +} + +export const LeftColumn: FunctionComponent = ({ children, ...rest }) => { + return ( + + {children} + + ); +}; + +export const CenterColumn: FunctionComponent = ({ children, ...rest }) => { + return ( + + {children} + + ); +}; + +export const RightColumn: FunctionComponent = ({ children, ...rest }) => { + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/markdown_renderers.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/markdown_renderers.tsx new file mode 100644 index 0000000000000..2e321e8bfc36f --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/markdown_renderers.tsx @@ -0,0 +1,70 @@ +/* + * 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 { + EuiCodeBlock, + EuiLink, + EuiTableHeaderCell, + EuiTableRow, + EuiTableRowCell, + EuiText, +} from '@elastic/eui'; +import React from 'react'; + +/** prevents links to the new pages from accessing `window.opener` */ +const REL_NOOPENER = 'noopener'; + +/** prevents search engine manipulation by noting the linked document is not trusted or endorsed by us */ +const REL_NOFOLLOW = 'nofollow'; + +/** prevents the browser from sending the current address as referrer via the Referer HTTP header */ +const REL_NOREFERRER = 'noreferrer'; + +export const markdownRenderers = { + root: ({ children }: { children: React.ReactNode[] }) => ( + {children} + ), + table: ({ children }: { children: React.ReactNode[] }) => ( + + {children} +
+ ), + tableRow: ({ children }: { children: React.ReactNode[] }) => ( + {children} + ), + tableCell: ({ isHeader, children }: { isHeader: boolean; children: React.ReactNode[] }) => { + return isHeader ? ( + {children} + ) : ( + {children} + ); + }, + // the headings used in markdown don't match our page so mapping them to the appropriate one + heading: ({ level, children }: { level: number; children: React.ReactNode[] }) => { + switch (level) { + case 1: + return

{children}

; + case 2: + return

{children}

; + case 3: + return
{children}
; + default: + return
{children}
; + } + }, + link: ({ children, href }: { children: React.ReactNode[]; href?: string }) => ( + + {children} + + ), + code: ({ language, value }: { language: string; value: string }) => { + return ( + + {value} + + ); + }, +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/overview_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/overview_panel.tsx new file mode 100644 index 0000000000000..ca6aceabe7f36 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/overview_panel.tsx @@ -0,0 +1,21 @@ +/* + * 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 { EuiSpacer } from '@elastic/eui'; +import React, { Fragment } from 'react'; +import { PackageInfo } from '../../../../types'; +import { Readme } from './readme'; +import { Screenshots } from './screenshots'; + +export function OverviewPanel(props: PackageInfo) { + const { screenshots, readme, name, version } = props; + return ( + + {readme && } + + {screenshots && } + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/readme.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/readme.tsx new file mode 100644 index 0000000000000..72e2d779c39be --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/readme.tsx @@ -0,0 +1,67 @@ +/* + * 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 { EuiLoadingContent, EuiText } from '@elastic/eui'; +import React, { Fragment, useEffect, useState } from 'react'; +import ReactMarkdown from 'react-markdown'; +import { useLinks } from '../../hooks'; +import { ContentCollapse } from './content_collapse'; +import { markdownRenderers } from './markdown_renderers'; +import { sendGetFileByPath } from '../../../../hooks'; + +export function Readme({ + readmePath, + packageName, + version, +}: { + readmePath: string; + packageName: string; + version: string; +}) { + const [markdown, setMarkdown] = useState(undefined); + const { toRelativeImage } = useLinks(); + const handleImageUri = React.useCallback( + (uri: string) => { + const isRelative = + uri.indexOf('http://') === 0 || uri.indexOf('https://') === 0 ? false : true; + const fullUri = isRelative ? toRelativeImage({ packageName, version, path: uri }) : uri; + return fullUri; + }, + [toRelativeImage, packageName, version] + ); + + useEffect(() => { + sendGetFileByPath(readmePath).then(res => { + setMarkdown(res.data || ''); + }); + }, [readmePath]); + + return ( + + {markdown !== undefined ? ( + + + + ) : ( + + {/* simulates a long page of text loading */} +

+ +

+

+ +

+

+ +

+
+ )} +
+ ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/screenshots.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/screenshots.tsx new file mode 100644 index 0000000000000..10cf9c97723c0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/screenshots.tsx @@ -0,0 +1,77 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiImage, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import React, { Fragment } from 'react'; +import styled from 'styled-components'; +import { ScreenshotItem } from '../../../../types'; +import { useLinks } from '../../hooks'; + +interface ScreenshotProps { + images: ScreenshotItem[]; +} + +export function Screenshots(props: ScreenshotProps) { + const { toImage } = useLinks(); + const { images } = props; + + // for now, just get first image + const image = images[0]; + const hasCaption = image.title ? true : false; + + const getHorizontalPadding = (styledProps: any): number => + parseInt(styledProps.theme.eui.paddingSizes.xl, 10) * 2; + const getVerticalPadding = (styledProps: any): number => + parseInt(styledProps.theme.eui.paddingSizes.xl, 10) * 1.75; + const getPadding = (styledProps: any) => + hasCaption + ? `${styledProps.theme.eui.paddingSizes.xl} ${getHorizontalPadding( + styledProps + )}px ${getVerticalPadding(styledProps)}px` + : `${getHorizontalPadding(styledProps)}px ${getVerticalPadding(styledProps)}px`; + + const ScreenshotsContainer = styled(EuiFlexGroup)` + background: linear-gradient(360deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 100%), + ${styledProps => styledProps.theme.eui.euiColorPrimary}; + padding: ${styledProps => getPadding(styledProps)}; + flex: 0 0 auto; + border-radius: ${styledProps => styledProps.theme.eui.euiBorderRadius}; + `; + + // fixes ie11 problems with nested flex items + const NestedEuiFlexItem = styled(EuiFlexItem)` + flex: 0 0 auto !important; + `; + return ( + + +

Screenshots

+
+ + + {hasCaption && ( + + + {image.title} + + + + )} + + {/* By default EuiImage sets width to 100% and Figure to 22.5rem for size=l images, + set image to same width. Will need to update if size changes. + */} + + + +
+ ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx new file mode 100644 index 0000000000000..39a6fca2e4318 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/side_nav_links.tsx @@ -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 { EuiButtonEmpty, EuiButtonEmptyProps } from '@elastic/eui'; +import React, { Fragment } from 'react'; +import styled from 'styled-components'; +import { PackageInfo, entries, DetailViewPanelName, InstallStatus } from '../../../../types'; +import { useLinks, useGetPackageInstallStatus } from '../../hooks'; + +export type NavLinkProps = Pick & { + active: DetailViewPanelName; +}; + +const PanelDisplayNames: Record = { + overview: 'Overview', + 'data-sources': 'Data Sources', +}; + +export function SideNavLinks({ name, version, active }: NavLinkProps) { + const { toDetailView } = useLinks(); + const getPackageInstallStatus = useGetPackageInstallStatus(); + const packageInstallStatus = getPackageInstallStatus(name); + + return ( + + {entries(PanelDisplayNames).map(([panel, display]) => { + const Link = styled(EuiButtonEmpty).attrs({ + href: toDetailView({ name, version, panel }), + })` + font-weight: ${p => + active === panel + ? p.theme.eui.euiFontWeightSemiBold + : p.theme.eui.euiFontWeightRegular}; + `; + // don't display Data Sources tab if the package is not installed + if (packageInstallStatus !== InstallStatus.installed && panel === 'data-sources') + return null; + + return ( +
+ {display} +
+ ); + })} +
+ ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/category_facets.tsx new file mode 100644 index 0000000000000..e138f9f531a39 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/category_facets.tsx @@ -0,0 +1,36 @@ +/* + * 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 } from '@elastic/eui'; +import React from 'react'; +import { CategorySummaryItem, CategorySummaryList } from '../../../../types'; + +export function CategoryFacets({ + categories, + selectedCategory, + onCategoryChange, +}: { + categories: CategorySummaryList; + selectedCategory: string; + onCategoryChange: (category: CategorySummaryItem) => unknown; +}) { + const controls = ( + + {categories.map(category => ( + onCategoryChange(category)} + > + {category.title} + + ))} + + ); + + return controls; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx new file mode 100644 index 0000000000000..5638413e1a2b4 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/header.tsx @@ -0,0 +1,104 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + // @ts-ignore (elastic/eui#1557) & (elastic/eui#1262) EuiImage is not exported yet + EuiImage, + EuiPage, + EuiPageBody, + // @ts-ignore + EuiSearchBar, + EuiSpacer, + EuiText, + EuiTitle, + EuiPageProps, +} from '@elastic/eui'; +import React, { Fragment, useState } from 'react'; +import styled from 'styled-components'; +import { useLinks } from '../../hooks'; + +export type HeaderProps = Pick & { + onSearch: (userInput: string) => unknown; +}; + +const Page = styled(EuiPage)` + padding: 0; +`; + +export function Header({ restrictWidth, onSearch }: HeaderProps) { + const [searchTerm, setSearchTerm] = useState(''); + const searchBar = ( + { + setSearchTerm(userInput); + onSearch(userInput); + }} + /> + ); + + const left = ( + + + + + + {searchBar} + + + + + ); + const right = ; + + return ( + + + + {left} + {right} + + + + ); +} + +function HeroCopy() { + const Subtitle = styled(EuiText)` + color: ${props => props.theme.eui.euiColorDarkShade}; + `; + + return ( + + +

Add Your Data

+
+ Some creative copy about packages goes here. +
+ ); +} + +function HeroImage() { + const { toAssets } = useLinks(); + const FlexGroup = styled(EuiFlexGroup)` + margin-bottom: -2px; // puts image directly on EuiHorizontalRule + `; + return ( + + + + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/hooks.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/hooks.tsx new file mode 100644 index 0000000000000..c3e29f723dcba --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/hooks.tsx @@ -0,0 +1,47 @@ +/* + * 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 { useEffect, useRef, useState } from 'react'; +import { PackageList } from '../../../../types'; +import { fieldsToSearch, LocalSearch, searchIdField } from './search_packages'; + +export function useAllPackages(selectedCategory: string, categoryPackages: PackageList = []) { + const [allPackages, setAllPackages] = useState([]); + + useEffect(() => { + if (!selectedCategory) setAllPackages(categoryPackages); + }, [selectedCategory, categoryPackages]); + + return [allPackages, setAllPackages] as [typeof allPackages, typeof setAllPackages]; +} + +export function useLocalSearch(allPackages: PackageList) { + const localSearchRef = useRef(null); + + useEffect(() => { + if (!allPackages.length) return; + + const localSearch = new LocalSearch(searchIdField); + fieldsToSearch.forEach(field => localSearch.addIndex(field)); + localSearch.addDocuments(allPackages); + localSearchRef.current = localSearch; + }, [allPackages]); + + return localSearchRef; +} + +export function useInstalledPackages(allPackages: PackageList) { + const [installedPackages, setInstalledPackages] = useState([]); + + useEffect(() => { + setInstalledPackages(allPackages.filter(({ status }) => status === 'installed')); + }, [allPackages]); + + return [installedPackages, setInstalledPackages] as [ + typeof installedPackages, + typeof setInstalledPackages + ]; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx new file mode 100644 index 0000000000000..ae7ba5b9c94c1 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/index.tsx @@ -0,0 +1,115 @@ +/* + * 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 { EuiHorizontalRule, EuiPage, EuiPageBody, EuiSpacer } from '@elastic/eui'; +import React, { Fragment, useState } from 'react'; +import styled from 'styled-components'; +// import { PLUGIN } from '../../../common/constants'; +import { CategorySummaryItem, PackageList } from '../../../../types'; +import { PackageListGrid } from '../../components/package_list_grid'; +// import { useBreadcrumbs, useLinks } from '../../hooks'; +import { CategoryFacets } from './category_facets'; +import { Header } from './header'; +import { useAllPackages, useInstalledPackages, useLocalSearch } from './hooks'; +import { useGetCategories, useGetPackages } from '../../../../hooks'; +import { SearchPackages } from './search_packages'; + +export const FullBleedPage = styled(EuiPage)` + padding: 0; +`; + +export function Home() { + const maxContentWidth = 1200; + // useBreadcrumbs([{ text: PLUGIN.TITLE, href: toListView() }]); + + const state = useHomeState(); + const body = state.searchTerm ? ( + + ) : ( + + {state.installedPackages.length ? ( + + + + + ) : null} + + + ); + + return ( + +
+ + + + {body} + + + ); +} + +type HomeState = ReturnType; + +export function useHomeState() { + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCategory, setSelectedCategory] = useState(''); + const { data: categoriesRes } = useGetCategories(); + const categories = categoriesRes?.response; + const { data: categoryPackagesRes } = useGetPackages({ category: selectedCategory }); + const categoryPackages = categoryPackagesRes?.response; + const [allPackages, setAllPackages] = useAllPackages(selectedCategory, categoryPackages); + const localSearchRef = useLocalSearch(allPackages); + const [installedPackages, setInstalledPackages] = useInstalledPackages(allPackages); + + return { + searchTerm, + setSearchTerm, + selectedCategory, + setSelectedCategory, + categories, + allPackages, + setAllPackages, + installedPackages, + localSearchRef, + setInstalledPackages, + categoryPackages, + }; +} + +function InstalledPackages({ list }: { list: PackageList }) { + const title = 'Your Packages'; + + return ; +} + +function AvailablePackages({ + allPackages, + categories, + categoryPackages, + selectedCategory, + setSelectedCategory, +}: HomeState) { + const title = 'Available Packages'; + const noFilter = { + id: '', + title: 'All', + count: allPackages.length, + }; + + const controls = categories ? ( + setSelectedCategory(id)} + /> + ) : null; + + return ; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/search_packages.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/search_packages.tsx new file mode 100644 index 0000000000000..adffdefd30a4f --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/search_packages.tsx @@ -0,0 +1,33 @@ +/* + * 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 { Search as LocalSearch } from 'js-search'; +import React from 'react'; +import { PackageList, PackageListItem } from '../../../../types'; +import { SearchResults } from './search_results'; + +export { LocalSearch }; +export type SearchField = keyof PackageListItem; +export const searchIdField: SearchField = 'name'; +export const fieldsToSearch: SearchField[] = ['description', 'name', 'title']; + +interface SearchPackagesProps { + searchTerm: string; + localSearchRef: React.MutableRefObject; + allPackages: PackageList; +} + +export function SearchPackages({ searchTerm, localSearchRef, allPackages }: SearchPackagesProps) { + // this means the search index hasn't been built yet. + // i.e. the intial fetch of all packages hasn't finished + if (!localSearchRef.current) return
Still fetching matches. Try again in a moment.
; + + const matches = localSearchRef.current.search(searchTerm) as PackageList; + const matchingIds = matches.map(match => match[searchIdField]); + const filtered = allPackages.filter(item => matchingIds.includes(item[searchIdField])); + + return ; +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/search_results.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/search_results.tsx new file mode 100644 index 0000000000000..fbdcaac01931b --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/home/search_results.tsx @@ -0,0 +1,33 @@ +/* + * 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 { EuiText, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { PackageList } from '../../../../types'; +import { PackageListGrid } from '../../components/package_list_grid'; + +interface SearchResultsProps { + term: string; + results: PackageList; +} + +export function SearchResults({ term, results }: SearchResultsProps) { + const title = 'Search results'; + return ( + + + {results.length} results for "{term}" + + + } + /> + ); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 531c29b523c1d..f3162513e0254 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -9,4 +9,5 @@ export { fleetSetupRouteService, agentRouteService, enrollmentAPIKeyRouteService, + epmRouteService, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index bef766c672034..be0246e4cb211 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -30,4 +30,36 @@ export { // API schemas - Enrollment API Keys GetEnrollmentAPIKeysResponse, GetOneEnrollmentAPIKeyResponse, + // EPM types + AssetReference, + AssetsGroupedByServiceByType, + AssetType, + AssetTypeToParts, + CategoryId, + CategorySummaryItem, + CategorySummaryList, + ElasticsearchAssetType, + KibanaAssetType, + PackageInfo, + PackageList, + PackageListItem, + PackagesGroupedByStatus, + RequirementsByServiceName, + RequirementVersion, + ScreenshotItem, + ServiceName, + GetCategoriesResponse, + GetPackagesResponse, + GetInfoResponse, + InstallPackageResponse, + DeletePackageResponse, + DetailViewPanelName, + InstallStatus, } from '../../../../common'; + +// Calling Object.entries(PackagesGroupedByStatus) gave `status: string` +// which causes a "string is not assignable to type InstallationStatus` error +// see https://github.com/Microsoft/TypeScript/issues/20322 +// and https://github.com/Microsoft/TypeScript/pull/12253#issuecomment-263132208 +// and https://github.com/Microsoft/TypeScript/issues/21826#issuecomment-479851685 +export const entries = Object.entries as (o: T) => Array<[keyof T, T[keyof T]]>; diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts index 3aadb06128100..6af0f6e9bad9f 100644 --- a/x-pack/plugins/ingest_manager/server/constants/index.ts +++ b/x-pack/plugins/ingest_manager/server/constants/index.ts @@ -20,6 +20,7 @@ export { DATASOURCE_SAVED_OBJECT_TYPE, OUTPUT_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, + INDEX_PATTERN_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, // Defaults DEFAULT_AGENT_CONFIG_ID, diff --git a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts index 5640e70beeb91..6baaf70bac28f 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/agent/agent.ts @@ -5,7 +5,7 @@ */ import Handlebars from 'handlebars'; -import { VarsEntry } from '../../../../common/types'; +import { VarsEntry } from '../../../types'; /** * This takes a dataset object as input and merges it with the input template. diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts index e22a889f27333..1e901d912abf2 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Dataset } from '../../../../common/types'; +import { Dataset } from '../../../types'; import { getDatasetAssetBaseName } from './index'; test('getBaseName', () => { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts index af868f41f6fc7..7cd6fe3c3caa6 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/index.ts @@ -5,7 +5,7 @@ */ // Default label to be used as the use case -import { Dataset } from '../../../../common/types'; +import { Dataset } from '../../../types'; const DEFAULT_LABEL = 'default'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.ts index 047ee530d527e..0a2cfd7fc72ca 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/ingest_pipeline/ingest_pipelines.ts @@ -9,7 +9,7 @@ import { Dataset, ElasticsearchAssetType, IngestAssetType, -} from '../../../../../common/types'; +} from '../../../../types'; import * as Registry from '../../registry'; import { CallESAsCurrentUser } from '../../cluster_access'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts index 2b41a79a69c99..be05eb2deabc9 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - AssetReference, - Dataset, - RegistryPackage, - IngestAssetType, -} from '../../../../../common/types'; +import { AssetReference, Dataset, RegistryPackage, IngestAssetType } from '../../../../types'; import { CallESAsCurrentUser } from '../../cluster_access'; import { Field, loadFieldsFromYaml } from '../../fields/field'; import { getPipelineNameForInstallation } from '../ingest_pipeline/ingest_pipelines'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts index d56d0b82a83f0..4114877d5cbee 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts @@ -5,7 +5,7 @@ */ import { Field } from '../../fields/field'; -import { Dataset } from '../../../../../common/types'; +import { Dataset } from '../../../../types'; import { getDatasetAssetBaseName } from '../index'; export interface Template { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts index d90d2e6dc9df7..eb515f5652f36 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts @@ -5,7 +5,7 @@ */ import { safeLoad } from 'js-yaml'; -import { RegistryPackage } from '../../../../common/types'; +import { RegistryPackage } from '../../../types'; import { getAssetsData } from '../packages/assets'; // This should become a copy of https://github.com/elastic/beats/blob/d9a4c9c240a9820fab15002592e5bb6db318543b/libbeat/mapping/field.go#L39 diff --git a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts index f59b0764d8a43..61a82578f12a3 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/kibana/index_pattern/install.ts @@ -5,11 +5,11 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../../common/constants'; +import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../constants'; import * as Registry from '../../registry'; import { loadFieldsFromYaml, Fields, Field } from '../../fields/field'; import { getPackageKeysByStatus } from '../../packages/get'; -import { InstallationStatus, RegistryPackage } from '../../../../../common/types'; +import { InstallationStatus, RegistryPackage } from '../../../../types'; interface FieldFormatMap { [key: string]: FieldFormatMapItem; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts index 1fa879d8f4177..5153f9205dde7 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RegistryPackage } from '../../../../common/types'; +import { RegistryPackage } from '../../../types'; import { getAssets } from './assets'; const tests = [ diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts index a9d3701f7449f..13df63c8bc872 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/assets.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RegistryPackage } from '../../../../common/types'; +import { RegistryPackage } from '../../../types'; import * as Registry from '../registry'; import { cacheHas } from '../registry/cache'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index 4f86918a2b4fc..50724c9ae21e9 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -5,13 +5,8 @@ */ import { SavedObjectsClientContract } from 'src/core/server/'; -import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../common/constants'; -import { - Installation, - Installed, - NotInstalled, - InstallationStatus, -} from '../../../../common/types'; +import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; +import { Installation, InstallationStatus, PackageInfo } from '../../../types'; import * as Registry from '../registry'; import { createInstallableFrom } from './index'; @@ -69,7 +64,7 @@ export async function getPackageKeysByStatus( export async function getPackageInfo(options: { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; -}): Promise { +}): Promise { const { savedObjectsClient, pkgkey } = options; const [item, savedObject] = await Promise.all([ Registry.fetchInfo(pkgkey), diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts index ddad9f1ff2240..e0424aa8a36f5 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get_objects.ts @@ -5,7 +5,7 @@ */ import { SavedObject, SavedObjectsBulkCreateObject } from 'src/core/server/'; -import { AssetType } from '../../../../common/types'; +import { AssetType } from '../../../types'; import * as Registry from '../registry'; type ArchiveAsset = Pick; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts index 55260c917ab30..1103c70c24ae4 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/index.ts @@ -7,12 +7,11 @@ import { SavedObject } from '../../../../../../../src/core/server'; import { AssetType, - // ElasticsearchAssetType, Installable, Installation, InstallationStatus, KibanaAssetType, -} from '../../../../common/types'; +} from '../../../types'; export { getCategories, diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index efc335973827b..08d55f23d84e6 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -5,8 +5,8 @@ */ import { SavedObject, SavedObjectsClientContract } from 'src/core/server/'; -import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../common/constants'; -import { AssetReference, Installation, KibanaAssetType } from '../../../../common/types'; +import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AssetReference, Installation, KibanaAssetType } from '../../../types'; import { installIndexPatterns } from '../kibana/index_pattern/install'; import * as Registry from '../registry'; import { getObject } from './get_objects'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index 703daba06eed9..497d879907d80 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -5,8 +5,8 @@ */ import { SavedObjectsClientContract } from 'src/core/server/'; -import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../common/constants'; -import { AssetReference, AssetType, ElasticsearchAssetType } from '../../../../common/types'; +import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AssetReference, AssetType, ElasticsearchAssetType } from '../../../types'; import { CallESAsCurrentUser } from '../cluster_access'; import { getInstallation, savedObjectTypes } from './index'; import { installIndexPatterns } from '../kibana/index_pattern/install'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts index d001711481204..eae84275a49b9 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AssetParts } from '../../../../common/types'; +import { AssetParts } from '../../../types'; import { pathParts } from './index'; const testPaths = [ diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index f0f4fb702370b..70b2ef377e4ea 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -14,7 +14,7 @@ import { KibanaAssetType, RegistryPackage, RegistrySearchResults, -} from '../../../../common/types'; +} from '../../../types'; import { configService } from '../../'; import { cacheGet, cacheSet } from './cache'; import { ArchiveEntry, untarBuffer } from './extract'; diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index c69f2fe055c25..c7539ef5dd0bc 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -22,6 +22,23 @@ export { OutputType, EnrollmentAPIKeySOAttributes, EnrollmentAPIKey, + Installation, + InstallationStatus, + PackageInfo, + VarsEntry, + Dataset, + AssetReference, + ElasticsearchAssetType, + IngestAssetType, + RegistryPackage, + AssetType, + Installable, + KibanaAssetType, + AssetParts, + AssetsGroupedByServiceByType, + CategoryId, + CategorySummaryList, + RegistrySearchResults, // Agent constants AGENT_TYPE_PERMANENT, AGENT_TYPE_EPHEMERAL,