From f5e87246475171083e99e96ff3052c19151a2d0c Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Fri, 8 Jul 2022 17:34:38 +0100 Subject: [PATCH 01/22] [Fleet] Display package verification status (#135928) * add attributes.type to fleet error * return verification key ID as part of status response * show verification status of installed integrations * badge on installed integrations tab * show unverified status on overview page * Do now show labels on available packages list * show release label and verificatiomn status * update stories * self review * fix badge label * update openapi * add unit tests for verification function * review comments --- .../plugins/fleet/common/openapi/bundled.json | 3 + .../plugins/fleet/common/openapi/bundled.yaml | 2 + .../schemas/fleet_status_response.yaml | 2 + .../plugins/fleet/common/types/models/epm.ts | 2 + .../fleet/common/types/rest_spec/error.ts | 16 +++ .../common/types/rest_spec/fleet_setup.ts | 1 + .../fleet/common/types/rest_spec/index.ts | 15 +- .../integrations/layouts/default.tsx | 73 ++++++---- .../epm/components/package_card.stories.tsx | 60 +++----- .../sections/epm/components/package_card.tsx | 29 +++- .../epm/components/package_list_grid.tsx | 12 +- .../epm/screens/detail/overview/overview.tsx | 31 +++- .../epm/screens/home/available_packages.tsx | 14 +- .../sections/epm/screens/home/index.tsx | 59 ++++++-- .../epm/screens/home/installed_packages.tsx | 70 ++++++--- .../fleet/public/hooks/use_fleet_status.tsx | 2 + x-pack/plugins/fleet/public/services/index.ts | 2 +- .../services/package_verification.test.ts | 135 ++++++++++++++++++ .../public/services/package_verification.ts | 26 ++++ .../plugins/fleet/server/errors/handlers.ts | 2 +- x-pack/plugins/fleet/server/errors/index.ts | 7 +- .../fleet/server/routes/setup/handlers.ts | 7 + .../epm/packages/package_verification.ts | 8 ++ 23 files changed, 453 insertions(+), 125 deletions(-) create mode 100644 x-pack/plugins/fleet/common/types/rest_spec/error.ts create mode 100644 x-pack/plugins/fleet/public/services/package_verification.test.ts create mode 100644 x-pack/plugins/fleet/public/services/package_verification.ts diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 0b32651376f95..53e1b9f46568d 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -4082,6 +4082,9 @@ "encrypted_saved_object_encryption_key_required" ] } + }, + "package_verification_key_id": { + "type": "string" } }, "required": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index d2044f9e631ce..20fe96ac4cd2d 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -2557,6 +2557,8 @@ components: type: string enum: - encrypted_saved_object_encryption_key_required + package_verification_key_id: + type: string required: - isReady - missing_requirements diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/fleet_status_response.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/fleet_status_response.yaml index cf0f0f60084da..8bb00fdce58d3 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/fleet_status_response.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/fleet_status_response.yaml @@ -18,6 +18,8 @@ properties: type: string enum: - 'encrypted_saved_object_encryption_key_required' + package_verification_key_id: + type: string required: - isReady - missing_requirements diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index e177bcba0ebb2..2148f2ff4555c 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -406,6 +406,8 @@ export interface IntegrationCardItem { id: string; categories: string[]; fromIntegrations?: string; + isUnverified?: boolean; + showLabels?: boolean; } export type PackageVerificationStatus = 'verified' | 'unverified' | 'unknown'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/error.ts b/x-pack/plugins/fleet/common/types/rest_spec/error.ts new file mode 100644 index 0000000000000..c02f9cdb4db6c --- /dev/null +++ b/x-pack/plugins/fleet/common/types/rest_spec/error.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type FleetErrorType = 'verification_failed'; + +export interface FleetErrorResponse { + message: string; + statusCode: number; + attributes?: { + type?: FleetErrorType; + }; +} diff --git a/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts index 5204b7bfbdbd1..33929bd92c8b1 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/fleet_setup.ts @@ -16,4 +16,5 @@ export interface GetFleetStatusResponse { 'security_required' | 'tls_required' | 'api_keys' | 'fleet_admin_user' | 'fleet_server' >; missing_optional_features: Array<'encrypted_saved_object_encryption_key_required'>; + package_verification_key_id?: string; } diff --git a/x-pack/plugins/fleet/common/types/rest_spec/index.ts b/x-pack/plugins/fleet/common/types/rest_spec/index.ts index 78b9f09f7f9f8..989e65209bf9d 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/index.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/index.ts @@ -5,15 +5,16 @@ * 2.0. */ +export * from './agent_policy'; +export * from './agent'; +export * from './app'; export * from './common'; -export * from './package_policy'; export * from './data_stream'; -export * from './agent'; -export * from './agent_policy'; -export * from './fleet_setup'; -export * from './epm'; +export * from './download_sources'; export * from './enrollment_api_key'; +export * from './epm'; +export * from './error'; +export * from './fleet_setup'; export * from './output'; +export * from './package_policy'; export * from './settings'; -export * from './app'; -export * from './download_sources'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx index 2554a8b730938..24fdfee190512 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/layouts/default.tsx @@ -5,21 +5,61 @@ * 2.0. */ import React, { memo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import styled from 'styled-components'; + import { useLink } from '../../../hooks'; import type { Section } from '../sections'; import { WithHeaderLayout } from '.'; +const TabBadge = styled(EuiBadge)` + padding: 0 1px; + margin-left: 4px; +`; + +const TabTitle: React.FC<{ title: JSX.Element; hasWarning: boolean }> = memo( + ({ title, hasWarning }) => { + return ( + <> + {title} + {hasWarning && } + + ); + } +); interface Props { section?: Section; children?: React.ReactNode; + sectionsWithWarning?: Section[]; } -export const DefaultLayout: React.FunctionComponent = memo(({ section, children }) => { +export const DefaultLayout: React.FC = memo(({ section, children, sectionsWithWarning }) => { const { getHref } = useLink(); + const tabs = [ + { + name: ( + + ), + section: 'browse' as Section, + href: getHref('integrations_all'), + }, + { + name: ( + + ), + section: 'manage' as Section, + href: getHref('integrations_installed'), + }, + ]; return ( = memo(({ section, ch } - tabs={[ - { - name: ( - - ), - isSelected: section === 'browse', - href: getHref('integrations_all'), - }, - { - name: ( - - ), - isSelected: section === 'manage', - href: getHref('integrations_installed'), - }, - ]} + tabs={tabs.map((tab) => ({ + name: ( + + ), + href: tab.href, + isSelected: section === tab.section, + }))} > {children} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx index e62044b31333a..dee9dfbe4d42b 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx @@ -7,10 +7,6 @@ import React from 'react'; -import type { SavedObject } from '@kbn/core/public'; - -import type { Installation } from '../../../../../../common'; - import type { PackageCardProps } from './package_card'; import { PackageCard } from './package_card'; @@ -19,7 +15,7 @@ export default { description: 'A card representing a package available in Fleet', }; -type Args = Omit & { width: number }; +type Args = PackageCardProps & { width: number }; const args: Args = { width: 280, @@ -33,6 +29,7 @@ const args: Args = { icons: [], integration: '', categories: ['foobar'], + isUnverified: false, }; const argTypes = { @@ -42,48 +39,23 @@ const argTypes = { options: ['ga', 'beta', 'experimental'], }, }, + isUnverified: { + control: 'boolean', + }, }; -export const NotInstalled = ({ width, ...props }: Args) => ( +export const AvailablePackage = ({ width, ...props }: Args) => (
- {/* - // @ts-ignore */} - +
); +AvailablePackage.args = args; +AvailablePackage.argTypes = argTypes; -export const Installed = ({ width, ...props }: Args) => { - const savedObject: SavedObject = { - id: props.id, - // @ts-expect-error - type: props.type || '', - attributes: { - name: props.name, - version: props.version, - install_version: props.version, - es_index_patterns: {}, - installed_kibana: [], - installed_kibana_space_id: 'default', - installed_es: [], - install_status: 'installed', - install_source: 'registry', - install_started_at: '2020-01-01T00:00:00.000Z', - keep_policies_up_to_date: false, - verification_status: 'unknown', - }, - references: [], - }; - - return ( -
- {/* - // @ts-ignore */} - -
- ); -}; - -NotInstalled.args = args; -NotInstalled.argTypes = argTypes; -Installed.args = args; -Installed.argTypes = argTypes; +export const InstalledPackage = ({ width, ...props }: Args) => ( +
+ +
+); +InstalledPackage.args = args; +InstalledPackage.argTypes = argTypes; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx index 16296c928c710..fa96da7ea6ace 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.tsx @@ -7,10 +7,12 @@ import React from 'react'; import styled from 'styled-components'; -import { EuiCard, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiBadge, EuiCard, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; + import { CardIcon } from '../../../../../components/package_icon'; import type { IntegrationCardItem } from '../../../../../../common/types/models/epm'; @@ -38,6 +40,8 @@ export function PackageCard({ release, id, fromIntegrations, + isUnverified, + showLabels = true, }: PackageCardProps) { let releaseBadge: React.ReactNode | null = null; @@ -52,6 +56,24 @@ export function PackageCard({ ); } + let verifiedBadge: React.ReactNode | null = null; + + if (isUnverified && showLabels) { + verifiedBadge = ( + + + + + + + + + ); + } + const { application } = useStartServices(); const onCardClick = () => { @@ -88,7 +110,10 @@ export function PackageCard({ } onClick={onCardClick} > - {releaseBadge} + + {verifiedBadge} + {releaseBadge} + ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx index d9e258f70f902..09e28e649b84e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx @@ -39,6 +39,7 @@ export interface Props { onSearchChange: (search: string) => void; showMissingIntegrationMessage?: boolean; callout?: JSX.Element | null; + showCardLabels?: boolean; } export const PackageListGrid: FunctionComponent = ({ @@ -52,6 +53,7 @@ export const PackageListGrid: FunctionComponent = ({ showMissingIntegrationMessage = false, featuredList = null, callout, + showCardLabels = true, }) => { const [searchTerm, setSearchTerm] = useState(initialSearch || ''); const localSearchRef = useLocalSearch(list); @@ -104,6 +106,7 @@ export const PackageListGrid: FunctionComponent = ({ ); } @@ -180,16 +183,21 @@ function ControlsColumn({ controls, title, sticky }: ControlsColumnProps) { interface GridColumnProps { list: IntegrationCardItem[]; showMissingIntegrationMessage?: boolean; + showCardLabels?: boolean; } -function GridColumn({ list, showMissingIntegrationMessage = false }: GridColumnProps) { +function GridColumn({ + list, + showMissingIntegrationMessage = false, + showCardLabels = false, +}: GridColumnProps) { return ( {list.length ? ( list.map((item) => { return ( - + ); }) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx index 0b477fee2ba77..5433244d5c2c6 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx @@ -6,8 +6,12 @@ */ import React, { memo, useMemo } from 'react'; import styled from 'styled-components'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useFleetStatus } from '../../../../../../../hooks'; +import { isPackageUnverified } from '../../../../../../../services'; import type { PackageInfo, RegistryPolicyTemplate } from '../../../../../types'; import { Screenshots } from './screenshots'; @@ -26,16 +30,39 @@ const LeftColumn = styled(EuiFlexItem)` } `; +const UnverifiedCallout = () => ( + <> + +

+ +

+
+ + +); + export const OverviewPage: React.FC = memo(({ packageInfo, integrationInfo }) => { const screenshots = useMemo( () => integrationInfo?.screenshots || packageInfo.screenshots || [], [integrationInfo, packageInfo.screenshots] ); - + const { packageVerificationKeyId } = useFleetStatus(); + const isUnverified = isPackageUnverified(packageInfo, packageVerificationKeyId); return ( + {isUnverified && } {packageInfo.readme ? ( { // TODO: clintandrewhall - this component is hard to test due to the hooks, particularly those that use `http` // or `location` to load data. Ideally, we'll split this into "connected" and "pure" components. -export const AvailablePackages: React.FC = memo(() => { +export const AvailablePackages: React.FC<{ + allPackages?: GetPackagesResponse | null; + isLoading: boolean; +}> = ({ allPackages, isLoading }) => { const [preference, setPreference] = useState('recommended'); useBreadcrumbs('integrations_all'); @@ -238,7 +241,7 @@ export const AvailablePackages: React.FC = memo(() => { ]; const cards: IntegrationCardItem[] = eprAndCustomPackages.map((item) => { - return mapToCard(getAbsolutePath, getHref, item); + return mapToCard({ getAbsolutePath, getHref, item }); }); cards.sort((a, b) => { @@ -382,6 +385,7 @@ export const AvailablePackages: React.FC = memo(() => { onSearchChange={setSearchTerm} showMissingIntegrationMessage callout={noEprCallout} + showCardLabels={false} /> ); -}); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx index 4322f434ddc70..93d226f885119 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/index.tsx @@ -5,21 +5,28 @@ * 2.0. */ -import React, { memo } from 'react'; +import React, { useMemo } from 'react'; import { Switch, Route } from 'react-router-dom'; import type { IntegrationCategory } from '@kbn/custom-integrations-plugin/common'; import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common'; +import { installationStatuses } from '../../../../../../../common/constants'; + import type { DynamicPage, DynamicPagePathValues, StaticPage } from '../../../../constants'; import { INTEGRATIONS_ROUTING_PATHS, INTEGRATIONS_SEARCH_QUERYPARAM } from '../../../../constants'; import { DefaultLayout } from '../../../../layouts'; +import { isPackageUnverified } from '../../../../services'; import type { PackageListItem } from '../../../../types'; import type { IntegrationCardItem } from '../../../../../../../common/types/models'; +import { useGetPackages } from '../../../../hooks'; + +import type { Section } from '../../..'; + import type { CategoryFacet } from './category_facets'; import { InstalledPackages } from './installed_packages'; import { AvailablePackages } from './available_packages'; @@ -43,21 +50,29 @@ export const categoryExists = (category: string, categories: CategoryFacet[]) => return categories.some((c) => c.id === category); }; -export const mapToCard = ( - getAbsolutePath: (p: string) => string, - getHref: (page: StaticPage | DynamicPage, values?: DynamicPagePathValues) => string, - item: CustomIntegration | PackageListItem, - selectedCategory?: string -): IntegrationCardItem => { +export const mapToCard = ({ + getAbsolutePath, + getHref, + item, + packageVerificationKeyId, + selectedCategory, +}: { + getAbsolutePath: (p: string) => string; + getHref: (page: StaticPage | DynamicPage, values?: DynamicPagePathValues) => string; + item: CustomIntegration | PackageListItem; + packageVerificationKeyId?: string; + selectedCategory?: string; +}): IntegrationCardItem => { let uiInternalPathUrl; + let isUnverified = false; if (item.type === 'ui_link') { uiInternalPathUrl = item.uiExternalLink || getAbsolutePath(item.uiInternalPath); } else { let urlVersion = item.version; - if ('savedObject' in item) { urlVersion = item.savedObject.attributes.version || item.version; + isUnverified = isPackageUnverified(item, packageVerificationKeyId); } const url = getHref('integration_details_overview', { @@ -87,22 +102,38 @@ export const mapToCard = ( version: 'version' in item ? item.version || '' : '', release, categories: ((item.categories || []) as string[]).filter((c: string) => !!c), + isUnverified, }; }; -export const EPMHomePage: React.FC = memo(() => { +export const EPMHomePage: React.FC = () => { + const { data: allPackages, isLoading } = useGetPackages({ + experimental: true, + }); + + const installedPackages = useMemo( + () => + (allPackages?.response || []).filter((pkg) => pkg.status === installationStatuses.Installed), + [allPackages?.response] + ); + + const atLeastOneUnverifiedPackageInstalled = installedPackages.some( + (pkg) => 'savedObject' in pkg && pkg.savedObject.attributes.verification_status === 'unverified' + ); + + const sectionsWithWarning = (atLeastOneUnverifiedPackageInstalled ? ['manage'] : []) as Section[]; return ( - - + + - - + + ); -}); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/installed_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/installed_packages.tsx index 19de166cebc16..776aaaafd5a1e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/installed_packages.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/installed_packages.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { useLocation, useHistory, useParams } from 'react-router-dom'; import semverLt from 'semver/functions/lt'; import { i18n } from '@kbn/i18n'; @@ -13,11 +13,12 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; -import { installationStatuses } from '../../../../../../../common/constants'; import { pagePathGetters } from '../../../../constants'; -import { useGetPackages, useBreadcrumbs, useLink, useStartServices } from '../../../../hooks'; +import { useBreadcrumbs, useLink, useStartServices, useFleetStatus } from '../../../../hooks'; import { PackageListGrid } from '../../components/package_list_grid'; +import type { PackageListItem } from '../../../../types'; + import type { CategoryFacet } from './category_facets'; import { CategoryFacets } from './category_facets'; @@ -37,7 +38,7 @@ const AnnouncementLink = () => { ); }; -const Callout = () => ( +const InstalledIntegrationsInfoCallout = () => ( ( ); +const VerificationWarningCallout = () => ( + +

+ +

+
+); + // TODO: clintandrewhall - this component is hard to test due to the hooks, particularly those that use `http` // or `location` to load data. Ideally, we'll split this into "connected" and "pure" components. -export const InstalledPackages: React.FC = memo(() => { +export const InstalledPackages: React.FC<{ + installedPackages: PackageListItem[]; + isLoading: boolean; +}> = ({ installedPackages, isLoading }) => { useBreadcrumbs('integrations_installed'); - const { data: allPackages, isLoading } = useGetPackages({ - experimental: true, - }); + const { packageVerificationKeyId } = useFleetStatus(); const { getHref, getAbsolutePath } = useLink(); @@ -93,26 +113,20 @@ export const InstalledPackages: React.FC = memo(() => { ); } - const allInstalledPackages = useMemo( - () => - (allPackages?.response || []).filter((pkg) => pkg.status === installationStatuses.Installed), - [allPackages?.response] - ); - const updatablePackages = useMemo( () => - allInstalledPackages.filter( + installedPackages.filter( (item) => 'savedObject' in item && semverLt(item.savedObject.attributes.version, item.version) ), - [allInstalledPackages] + [installedPackages] ); const categories: CategoryFacet[] = useMemo( () => [ { ...INSTALLED_CATEGORY, - count: allInstalledPackages.length, + count: installedPackages.length, }, { id: 'updates_available', @@ -122,7 +136,7 @@ export const InstalledPackages: React.FC = memo(() => { }), }, ], - [allInstalledPackages.length, updatablePackages.length] + [installedPackages.length, updatablePackages.length] ); if (!categoryExists(selectedCategory, categories)) { @@ -142,10 +156,22 @@ export const InstalledPackages: React.FC = memo(() => { ); const cards = ( - selectedCategory === 'updates_available' ? updatablePackages : allInstalledPackages - ).map((item) => mapToCard(getAbsolutePath, getHref, item, selectedCategory || 'installed')); + selectedCategory === 'updates_available' ? updatablePackages : installedPackages + ).map((item) => + mapToCard({ + getAbsolutePath, + getHref, + item, + selectedCategory: selectedCategory || 'installed', + packageVerificationKeyId, + }) + ); - const callout = selectedCategory === 'updates_available' ? null : ; + const CalloutComponent = cards.some((c) => c.isUnverified) + ? VerificationWarningCallout + : InstalledIntegrationsInfoCallout; + const callout = + selectedCategory === 'updates_available' || isLoading ? null : ; return ( { list={cards} /> ); -}); +}; diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx index 7319cd9f07be7..29fa37f3b7270 100644 --- a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx @@ -19,6 +19,7 @@ interface FleetStatusState { error?: Error; missingRequirements?: GetFleetStatusResponse['missing_requirements']; missingOptionalFeatures?: GetFleetStatusResponse['missing_optional_features']; + packageVerificationKeyId?: GetFleetStatusResponse['package_verification_key_id']; } interface FleetStatus extends FleetStatusState { @@ -58,6 +59,7 @@ export const FleetStatusProvider: React.FC = ({ children }) => { isReady: res.data?.isReady ?? false, missingRequirements: res.data?.missing_requirements, missingOptionalFeatures: res.data?.missing_optional_features, + packageVerificationKeyId: res.data?.package_verification_key_id, })); } catch (error) { setState((s) => ({ ...s, isLoading: false, error })); diff --git a/x-pack/plugins/fleet/public/services/index.ts b/x-pack/plugins/fleet/public/services/index.ts index 2c1bedeaef82c..094fe2b66c6c9 100644 --- a/x-pack/plugins/fleet/public/services/index.ts +++ b/x-pack/plugins/fleet/public/services/index.ts @@ -41,7 +41,7 @@ export { countValidationErrors, getStreamsForInputType, } from '../../common'; - +export * from './package_verification'; export * from './pkg_key_from_package_info'; export * from './ui_extensions'; export * from './increment_policy_name'; diff --git a/x-pack/plugins/fleet/public/services/package_verification.test.ts b/x-pack/plugins/fleet/public/services/package_verification.test.ts new file mode 100644 index 0000000000000..d611eb6cccf0a --- /dev/null +++ b/x-pack/plugins/fleet/public/services/package_verification.test.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PackageVerificationStatus } from '../../common'; +import type { PackageInfo } from '../types'; + +import { ExperimentalFeaturesService, isPackageUnverified } from '.'; + +const mockGet = jest.spyOn(ExperimentalFeaturesService, 'get'); + +const createPackage = ({ + verificationStatus = 'unknown', + verificationKeyId, +}: { + verificationStatus?: PackageVerificationStatus; + verificationKeyId?: string; +} = {}): PackageInfo => ({ + name: 'test-package', + description: 'Test Package', + title: 'Test Package', + version: '0.0.1', + latestVersion: '0.0.1', + release: 'experimental', + format_version: '1.0.0', + owner: { github: 'elastic/fleet' }, + policy_templates: [], + // @ts-ignore + assets: {}, + savedObject: { + id: '1234', + type: 'epm-package', + references: [], + attributes: { + installed_kibana: [], + installed_es: [], + es_index_patterns: {}, + name: 'test-package', + version: '0.0.1', + install_status: 'installed', + install_version: '0.0.1', + install_started_at: new Date().toString(), + install_source: 'registry', + verification_status: verificationStatus, + ...(verificationKeyId && { verification_key_id: verificationKeyId }), + }, + }, +}); + +describe('isPackageUnverified', () => { + describe('When experimental feature is disabled', () => { + beforeEach(() => { + mockGet.mockReturnValue({ + packageVerification: false, + } as ReturnType); + }); + + it('Should return false for a package with no saved object', () => { + const noSoPkg = createPackage(); + // @ts-ignore we know pkg has savedObject but ts doesn't + delete noSoPkg.savedObject; + expect(isPackageUnverified(noSoPkg)).toEqual(false); + }); + it('Should return false for an unverified package', () => { + const unverifiedPkg = createPackage({ verificationStatus: 'unverified' }); + expect(isPackageUnverified(unverifiedPkg)).toEqual(false); + }); + it('Should return false for a verified package', () => { + const verifiedPkg = createPackage({ verificationStatus: 'verified' }); + expect(isPackageUnverified(verifiedPkg)).toEqual(false); + }); + it('Should return false for a verified package with correct key', () => { + const unverifiedPkg = createPackage({ + verificationStatus: 'verified', + verificationKeyId: '1234', + }); + expect(isPackageUnverified(unverifiedPkg, '1234')).toEqual(false); + }); + it('Should return false for a verified package with out of date key', () => { + const unverifiedPkg = createPackage({ + verificationStatus: 'verified', + verificationKeyId: '1234', + }); + expect(isPackageUnverified(unverifiedPkg, 'not_1234')).toEqual(false); + }); + it('Should return false for an unknown verification package', () => { + const unknownPkg = createPackage({ verificationStatus: 'unknown' }); + expect(isPackageUnverified(unknownPkg)).toEqual(false); + }); + }); + describe('When experimental feature is enabled', () => { + beforeEach(() => { + mockGet.mockReturnValue({ + packageVerification: true, + } as ReturnType); + }); + it('Should return false for a package with no saved object', () => { + const noSoPkg = createPackage(); + // @ts-ignore we know pkg has savedObject but ts doesn't + delete noSoPkg.savedObject; + expect(isPackageUnverified(noSoPkg)).toEqual(false); + }); + it('Should return false for a verified package', () => { + const unverifiedPkg = createPackage({ verificationStatus: 'verified' }); + expect(isPackageUnverified(unverifiedPkg)).toEqual(false); + }); + it('Should return false for an unknown verification package', () => { + const unverifiedPkg = createPackage({ verificationStatus: 'unknown' }); + expect(isPackageUnverified(unverifiedPkg)).toEqual(false); + }); + it('Should return false for a verified package with correct key', () => { + const unverifiedPkg = createPackage({ + verificationStatus: 'verified', + verificationKeyId: '1234', + }); + expect(isPackageUnverified(unverifiedPkg, '1234')).toEqual(false); + }); + + it('Should return true for an unverified package', () => { + const unverifiedPkg = createPackage({ verificationStatus: 'unverified' }); + expect(isPackageUnverified(unverifiedPkg)).toEqual(true); + }); + + it('Should return true for a verified package with out of date key', () => { + const unverifiedPkg = createPackage({ + verificationStatus: 'verified', + verificationKeyId: '1234', + }); + expect(isPackageUnverified(unverifiedPkg, 'not_1234')).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/services/package_verification.ts b/x-pack/plugins/fleet/public/services/package_verification.ts new file mode 100644 index 0000000000000..5b40a453be413 --- /dev/null +++ b/x-pack/plugins/fleet/public/services/package_verification.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PackageInfo, PackageListItem } from '../types'; + +import { ExperimentalFeaturesService } from '.'; + +export function isPackageUnverified( + pkg: PackageInfo | PackageListItem, + packageVerificationKeyId?: string +) { + if (!('savedObject' in pkg)) return false; + + const { verification_status: verificationStatus, verification_key_id: verificationKeyId } = + pkg.savedObject.attributes; + + const { packageVerification: isPackageVerificationEnabled } = ExperimentalFeaturesService.get(); + const isKeyOutdated = !!verificationKeyId && verificationKeyId !== packageVerificationKeyId; + const isUnverified = + verificationStatus === 'unverified' || (verificationStatus === 'verified' && isKeyOutdated); + return isPackageVerificationEnabled && isUnverified; +} diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index 1eb0aed4ad690..8b5f8463c9f41 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -84,7 +84,7 @@ export function ingestErrorToResponseOptions(error: IngestErrorHandlerParams['er logger.error(error.message); return { statusCode: getHTTPResponseCode(error), - body: { message: error.message }, + body: { message: error.message, attributes: error.attributes }, }; } diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index cbfcf5733f736..ecd5bb8c670ae 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -8,13 +8,15 @@ /* eslint-disable max-classes-per-file */ import type { ElasticsearchErrorDetails } from '@kbn/es-errors'; +import type { FleetErrorType } from '../../common'; + import { isESClientError } from './utils'; export { defaultIngestErrorHandler, ingestErrorToResponseOptions } from './handlers'; export { isESClientError } from './utils'; - export class IngestManagerError extends Error { + attributes?: { type?: FleetErrorType }; constructor(message?: string, public readonly meta?: unknown) { super(message); this.name = this.constructor.name; // for stack traces @@ -34,6 +36,9 @@ export class PackageOutdatedError extends IngestManagerError {} export class PackageFailedVerificationError extends IngestManagerError { constructor(pkgKey: string) { super(`${pkgKey} failed signature verification.`); + this.attributes = { + type: 'verification_failed', + }; } } export class AgentPolicyError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index 59a3516aac83a..2823c1b3873a5 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -11,6 +11,7 @@ import { formatNonFatalErrors, setupFleet } from '../../services/setup'; import { hasFleetServers } from '../../services/fleet_server'; import { defaultIngestErrorHandler } from '../../errors'; import type { FleetRequestHandler } from '../../types'; +import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification'; export const getFleetStatusHandler: FleetRequestHandler = async (context, request, response) => { try { @@ -43,6 +44,12 @@ export const getFleetStatusHandler: FleetRequestHandler = async (context, reques missing_optional_features: missingOptionalFeatures, }; + const packageVerificationKeyId = await getGpgKeyIdOrUndefined(); + + if (packageVerificationKeyId) { + body.package_verification_key_id = packageVerificationKeyId; + } + return response.ok({ body, }); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts b/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts index 0e39cefaf1169..b4432e8919d0c 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts @@ -24,6 +24,14 @@ interface VerificationResult { let cachedKey: openpgp.Key | undefined | null = null; +export async function getGpgKeyIdOrUndefined(): Promise { + const key = await getGpgKeyOrUndefined(); + + if (!key) return undefined; + + return key.getKeyID().toHex(); +} + export async function getGpgKeyOrUndefined(): Promise { if (cachedKey !== null) return cachedKey; From aee38f15f9aff47db76a40cfe50ee29b6a3e3a1a Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Fri, 8 Jul 2022 18:49:42 +0200 Subject: [PATCH 02/22] added logic to sanitize tags on UI (#136019) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/tag_options.test.tsx | 4 +- .../components/tag_options.tsx | 5 ++- .../components/tags_add_remove.test.tsx | 40 +++++++++++++++++++ .../components/tags_add_remove.tsx | 27 ++++++++----- .../agents/agent_list_page/utils/index.ts | 1 + .../utils/sanitize_tag.test.ts | 22 ++++++++++ .../agent_list_page/utils/sanitize_tag.ts | 10 +++++ 7 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.test.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.ts diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.test.tsx index 584a92a60310f..d4ad6f8fe144f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.test.tsx @@ -57,7 +57,7 @@ describe('TagOptions', () => { fireEvent.click(result.getByText('Delete tag')); expect(mockBulkUpdateTags).toHaveBeenCalledWith( - 'tags:agent', + 'tags:"agent"', [], ['agent'], expect.anything(), @@ -80,7 +80,7 @@ describe('TagOptions', () => { }); expect(mockBulkUpdateTags).toHaveBeenCalledWith( - 'tags:agent', + 'tags:"agent"', ['newName'], ['agent'], expect.anything(), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.tsx index 994fb1b64880e..7e25a7a098dba 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.tsx @@ -20,6 +20,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { useUpdateTags } from '../hooks'; +import { sanitizeTag } from '../utils'; interface Props { tagName: string; @@ -53,7 +54,7 @@ export const TagOptions: React.FC = ({ tagName, isTagHovered, onTagsUpdat const updateTagsHook = useUpdateTags(); const bulkUpdateTags = updateTagsHook.bulkUpdateTags; - const TAGS_QUERY = 'tags:{name}'; + const TAGS_QUERY = 'tags:"{name}"'; const handleRename = (newName?: string) => { if (newName === tagName || !newName) { @@ -127,7 +128,7 @@ export const TagOptions: React.FC = ({ tagName, isTagHovered, onTagsUpdat }} onChange={(e: ChangeEvent) => { const newName = e.currentTarget.value; - setUpdatedName(newName); + setUpdatedName(sanitizeTag(newName)); }} />
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx index 4467f8413d583..75e829d12b92e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx @@ -103,6 +103,46 @@ describe('TagsAddRemove', () => { ); }); + it('should add new tag by removing special chars', () => { + const result = renderComponent('agent1'); + const searchInput = result.getByRole('combobox'); + + fireEvent.input(searchInput, { + target: { value: 'Tag-123: _myTag"' }, + }); + + fireEvent.click(result.getAllByText('Create a new tag "Tag-123 _myTag"')[0].closest('button')!); + + expect(mockUpdateTags).toHaveBeenCalledWith( + 'agent1', + ['tag1', 'Tag-123 _myTag'], + expect.anything(), + 'Tag created', + 'Tag creation failed' + ); + }); + + it('should limit new tag to 20 length', () => { + const result = renderComponent('agent1'); + const searchInput = result.getByRole('combobox'); + + fireEvent.input(searchInput, { + target: { value: '01234567890123456789123' }, + }); + + fireEvent.click( + result.getAllByText('Create a new tag "01234567890123456789"')[0].closest('button')! + ); + + expect(mockUpdateTags).toHaveBeenCalledWith( + 'agent1', + ['tag1', '01234567890123456789'], + expect.anything(), + 'Tag created', + 'Tag creation failed' + ); + }); + it('should add selected tag when previously unselected - bulk selection', async () => { mockBulkUpdateTags.mockImplementation(() => { selectedTags = ['tag1', 'tag2']; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx index c60c6cec19fd9..c52ad66f2e9d0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx @@ -22,6 +22,8 @@ import { i18n } from '@kbn/i18n'; import { useUpdateTags } from '../hooks'; +import { sanitizeTag } from '../utils'; + import { TagOptions } from './tag_options'; interface Props { @@ -138,8 +140,9 @@ export const TagsAddRemove: React.FC = ({ defaultMessage: 'Find or create label...', }), onChange: (value: string) => { - setSearchValue(value); + setSearchValue(sanitizeTag(value)); }, + value: searchValue ?? '', }} options={labels} renderOption={renderOption} @@ -162,14 +165,20 @@ export const TagsAddRemove: React.FC = ({ ); }} > - {' '} - + + + + + + + + } > diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/index.ts index 192b4a5593b34..a269d6a68c689 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/index.ts @@ -7,3 +7,4 @@ export * from './truncate_tag'; export * from './get_common_tags'; +export * from './sanitize_tag'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.test.ts new file mode 100644 index 0000000000000..8e07fd8e04b99 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.test.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sanitizeTag } from './sanitize_tag'; + +describe('sanitizeTag', () => { + it('should remove special characters from tag name', () => { + expect(sanitizeTag('Tag-123: []"\'#$%^&*__')).toEqual('Tag-123 __'); + }); + + it('should limit tag to 20 length', () => { + expect(sanitizeTag('aaaa aaaa aaaa aaaa bbb')).toEqual('aaaa aaaa aaaa aaaa '); + }); + + it('should do nothing for empty tag', () => { + expect(sanitizeTag('')).toEqual(''); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.ts new file mode 100644 index 0000000000000..d0b00ea9bed19 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/utils/sanitize_tag.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function sanitizeTag(tag: string): string { + return tag.replace(/[^a-zA-Z0-9 \-_]/g, '').slice(0, 20); +} From 6c08eacdc0cf23a9ec645afec300b926cc2028ba Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 8 Jul 2022 12:57:16 -0400 Subject: [PATCH 03/22] [File Upload] [Maps] Reduce precision of coordinates for geo imports (#135133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reduce precision of coordinates for geo imports * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Revert to jsts@1.6.2 The jsts library does not transpile modules since 2.0. So it is not currently possible to use the newer library. * Fix yarn lockfile * Fix test Test runs on features, not feature collections. 😬 Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../geo/geojson_clean_and_validate.js | 11 +++++- .../geo/geojson_clean_and_validate.test.js | 38 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.js b/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.js index 17b4f94b52d5f..e16374d851de8 100644 --- a/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.js +++ b/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.js @@ -8,6 +8,13 @@ import * as jsts from 'jsts'; import rewind from '@mapbox/geojson-rewind'; +// The GeoJSON specification suggests limiting coordinate precision to six decimal places +// See https://datatracker.ietf.org/doc/html/rfc7946#section-11.2 +// We can enforce rounding to six decimal places by setting the PrecisionModel scale +// scale = 10^n where n = maximum number of decimal places +const precisionModel = new jsts.geom.PrecisionModel(Math.pow(10, 6)); +const geometryPrecisionReducer = new jsts.precision.GeometryPrecisionReducer(precisionModel); +geometryPrecisionReducer.setChangePrecisionModel(true); const geoJSONReader = new jsts.io.GeoJSONReader(); const geoJSONWriter = new jsts.io.GeoJSONWriter(); @@ -36,6 +43,8 @@ export function cleanGeometry({ geometry }) { if (!geometry) { return null; } - const geometryToWrite = geometry.isSimple() || geometry.isValid() ? geometry : geometry.buffer(0); + + // GeometryPrecisionReducer will automatically clean invalid geometries + const geometryToWrite = geometryPrecisionReducer.reduce(geometry); return geoJSONWriter.write(geometryToWrite); } diff --git a/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.test.js b/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.test.js index 0f8d126251dfb..8c9000e66e811 100644 --- a/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.test.js +++ b/x-pack/plugins/file_upload/public/importer/geo/geojson_clean_and_validate.test.js @@ -102,6 +102,44 @@ describe('geo_json_clean_and_validate', () => { }); }); + it('should reduce coordinate precision', () => { + const ludicrousPrecisionGeoJson = { + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [108.28125, 61.77312286453146], + [72.0703125, 46.31658418182218], + [99.49218749999999, 22.917922936146045], + [133.2421875, 27.059125784374068], + [139.5703125, 52.908902047770255], + [108.28125, 61.77312286453146], + ], + ], + }, + }; + + expect(geoJsonCleanAndValidate(ludicrousPrecisionGeoJson)).toEqual({ + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [108.28125, 61.773123], + [72.070313, 46.316584], + [99.492187, 22.917923], + [133.242188, 27.059126], + [139.570313, 52.908902], + [108.28125, 61.773123], + ], + ], + }, + }); + }); + it('should reverse counter-clockwise winding order', () => { const counterClockwiseGeoJson = { type: 'Feature', From 87ac0fd2feb30f6d659a9aa023f816fdb0fd9b3a Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Fri, 8 Jul 2022 12:02:59 -0500 Subject: [PATCH 04/22] [docker] Add ubi9 image (#135868) * [docker] Add ubi9 image * update artifacts tests * cleanup * fixes * formatting --- .buildkite/pipelines/artifacts.yml | 12 +++++++++++- .buildkite/pipelines/docker_context.yml | 11 ----------- .../scripts/steps/artifacts/docker_context.sh | 4 +++- .../tasks/os_packages/create_os_package_tasks.ts | 16 +++++++++++++--- .../tasks/os_packages/docker_generator/run.ts | 9 +++++---- .../docker_generator/template_context.ts | 2 +- .../templates/dockerfile.template.ts | 4 ++-- 7 files changed, 35 insertions(+), 23 deletions(-) delete mode 100644 .buildkite/pipelines/docker_context.yml diff --git a/.buildkite/pipelines/artifacts.yml b/.buildkite/pipelines/artifacts.yml index bfe5fe190ea16..8e08c736694e8 100644 --- a/.buildkite/pipelines/artifacts.yml +++ b/.buildkite/pipelines/artifacts.yml @@ -51,7 +51,17 @@ steps: - exit_status: '*' limit: 1 - - command: KIBANA_DOCKER_CONTEXT=ubi .buildkite/scripts/steps/artifacts/docker_context.sh + - command: KIBANA_DOCKER_CONTEXT=ubi8 .buildkite/scripts/steps/artifacts/docker_context.sh + label: 'Docker Context Verification' + agents: + queue: n2-2 + timeout_in_minutes: 30 + retry: + automatic: + - exit_status: '*' + limit: 1 + + - command: KIBANA_DOCKER_CONTEXT=ubi9 .buildkite/scripts/steps/artifacts/docker_context.sh label: 'Docker Context Verification' agents: queue: n2-2 diff --git a/.buildkite/pipelines/docker_context.yml b/.buildkite/pipelines/docker_context.yml deleted file mode 100644 index f85b895e4780b..0000000000000 --- a/.buildkite/pipelines/docker_context.yml +++ /dev/null @@ -1,11 +0,0 @@ - steps: - - command: .buildkite/scripts/steps/docker_context/build.sh - label: 'Docker Build Context' - agents: - queue: n2-4 - timeout_in_minutes: 30 - key: build-docker-context - retry: - automatic: - - exit_status: '*' - limit: 1 \ No newline at end of file diff --git a/.buildkite/scripts/steps/artifacts/docker_context.sh b/.buildkite/scripts/steps/artifacts/docker_context.sh index 1195d7ad5dc38..86c4361173a08 100755 --- a/.buildkite/scripts/steps/artifacts/docker_context.sh +++ b/.buildkite/scripts/steps/artifacts/docker_context.sh @@ -19,8 +19,10 @@ if [[ "$KIBANA_DOCKER_CONTEXT" == "default" ]]; then DOCKER_CONTEXT_FILE="kibana-$FULL_VERSION-docker-build-context.tar.gz" elif [[ "$KIBANA_DOCKER_CONTEXT" == "cloud" ]]; then DOCKER_CONTEXT_FILE="kibana-cloud-$FULL_VERSION-docker-build-context.tar.gz" -elif [[ "$KIBANA_DOCKER_CONTEXT" == "ubi" ]]; then +elif [[ "$KIBANA_DOCKER_CONTEXT" == "ubi8" ]]; then DOCKER_CONTEXT_FILE="kibana-ubi8-$FULL_VERSION-docker-build-context.tar.gz" +elif [[ "$KIBANA_DOCKER_CONTEXT" == "ubi9" ]]; then + DOCKER_CONTEXT_FILE="kibana-ubi9-$FULL_VERSION-docker-build-context.tar.gz" fi tar -xf "target/$DOCKER_CONTEXT_FILE" -C "$DOCKER_BUILD_FOLDER" diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 49967feb214d6..69a272d39f4a0 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -86,7 +86,13 @@ export const CreateDockerUBI: Task = { async run(config, log, build) { await runDockerGenerator(config, log, build, { architecture: 'x64', - baseImage: 'ubi', + baseImage: 'ubi8', + context: false, + image: true, + }); + await runDockerGenerator(config, log, build, { + architecture: 'x64', + baseImage: 'ubi9', context: false, image: true, }); @@ -124,9 +130,13 @@ export const CreateDockerContexts: Task = { image: false, dockerBuildDate, }); - await runDockerGenerator(config, log, build, { - baseImage: 'ubi', + baseImage: 'ubi8', + context: true, + image: false, + }); + await runDockerGenerator(config, log, build, { + baseImage: 'ubi9', context: true, image: false, }); diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index d8b604f00b46e..34b58e7513bb1 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -29,7 +29,7 @@ export async function runDockerGenerator( build: Build, flags: { architecture?: string; - baseImage: 'none' | 'ubi' | 'ubuntu'; + baseImage: 'none' | 'ubi9' | 'ubi8' | 'ubuntu'; context: boolean; image: boolean; ironbank?: boolean; @@ -39,11 +39,12 @@ export async function runDockerGenerator( ) { let baseImageName = ''; if (flags.baseImage === 'ubuntu') baseImageName = 'ubuntu:20.04'; - if (flags.baseImage === 'ubi') baseImageName = 'docker.elastic.co/ubi8/ubi-minimal:latest'; - const ubiVersionTag = 'ubi8'; + if (flags.baseImage === 'ubi8') baseImageName = 'docker.elastic.co/ubi8/ubi-minimal:latest'; + if (flags.baseImage === 'ubi9') baseImageName = 'docker.elastic.co/ubi9/ubi-minimal:latest'; let imageFlavor = ''; - if (flags.baseImage === 'ubi') imageFlavor += `-${ubiVersionTag}`; + if (flags.baseImage === 'ubi8') imageFlavor += `-ubi8`; + if (flags.baseImage === 'ubi9') imageFlavor += `-ubi9`; if (flags.ironbank) imageFlavor += '-ironbank'; if (flags.cloud) imageFlavor += '-cloud'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts index 32a551820a05b..da2d7422a03ea 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts @@ -23,7 +23,7 @@ export interface TemplateContext { dockerBuildDate: string; usePublicArtifact?: boolean; publicArtifactSubdomain: string; - baseImage: 'none' | 'ubi' | 'ubuntu'; + baseImage: 'none' | 'ubi8' | 'ubi9' | 'ubuntu'; baseImageName: string; cloud?: boolean; metricbeatTarball?: string; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts index 63b04ed6f70b0..ca597e5c38941 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts @@ -16,8 +16,8 @@ function generator(options: TemplateContext) { const dir = options.ironbank ? 'ironbank' : 'base'; const template = readFileSync(resolve(__dirname, dir, './Dockerfile')); return Mustache.render(template.toString(), { - packageManager: options.baseImage === 'ubi' ? 'microdnf' : 'apt-get', - ubi: options.baseImage === 'ubi', + packageManager: options.baseImage.includes('ubi') ? 'microdnf' : 'apt-get', + ubi: options.baseImage.includes('ubi'), ubuntu: options.baseImage === 'ubuntu', ...options, }); From 3891aeb95fb030348807dee583e0c217a9083b7d Mon Sep 17 00:00:00 2001 From: Andrew Tate Date: Fri, 8 Jul 2022 12:07:43 -0500 Subject: [PATCH 05/22] [Chart expressions] new metric vis expression (#135461) --- .i18nrc.json | 15 +- docs/developer/plugin-list.asciidoc | 4 + packages/kbn-optimizer/limits.yml | 1 + .../expression_legacy_metric/.i18nrc.json | 6 + .../.storybook/main.js | 26 + .../expression_legacy_metric/README.md | 9 + .../common/constants.ts | 14 + .../metric_vis_function.test.ts.snap | 2 +- .../common/expression_functions/index.ts | 9 + .../metric_vis_function.test.ts | 0 .../metric_vis_function.ts | 206 ++++ .../expression_legacy_metric/common/index.ts | 25 + .../common/types/expression_functions.ts | 48 + .../common/types/expression_renderers.ts | 59 ++ .../common/types/index.ts | 10 + .../expression_legacy_metric/jest.config.js | 19 + .../expression_legacy_metric/kibana.json | 21 + .../public/__mocks__/format_service.ts | 0 .../public/__mocks__/palette_service.ts | 0 .../public/__mocks__/services.ts | 0 .../__stories__/metric_renderer.stories.tsx | 0 .../metric_component.test.tsx.snap | 2 +- .../with_auto_scale.test.tsx.snap | 0 .../public/components/metric.scss | 17 +- .../components/metric_component.test.tsx | 0 .../public/components/metric_component.tsx | 0 .../public/components/metric_value.test.tsx | 6 +- .../public/components/metric_value.tsx | 12 +- .../components/with_auto_scale.styles.ts | 0 .../components/with_auto_scale.test.tsx | 0 .../public/components/with_auto_scale.tsx | 0 .../public/expression_renderers/index.ts | 9 + .../metric_vis_renderer.tsx | 91 ++ .../public/format_service.ts | 0 .../expression_legacy_metric/public/index.ts | 13 + .../expression_legacy_metric/public/plugin.ts | 41 + .../public/services/format_service.ts | 13 + .../public/services/index.ts | 10 + .../public/services/palette_service.ts | 13 + .../public/utils/format.ts | 21 + .../public/utils/index.ts | 9 + .../public/utils/palette.ts | 41 + .../expression_legacy_metric/server/index.ts | 13 + .../expression_legacy_metric/server/plugin.ts | 40 + .../expression_legacy_metric/tsconfig.json | 23 + .../metric_vis_function.ts | 197 ++-- .../common/types/expression_functions.ts | 25 +- .../common/types/expression_renderers.ts | 34 +- .../expression_metric/kibana.json | 10 +- .../public/__mocks__/theme_service.ts | 13 + .../public/components/currency_codes.test.ts | 33 + .../public/components/currency_codes.ts | 46 + .../public/components/metric_vis.test.tsx | 905 ++++++++++++++++++ .../public/components/metric_vis.tsx | 240 +++++ .../metric_vis_renderer.tsx | 62 +- .../expression_metric/public/plugin.ts | 12 +- .../public/services/index.ts | 2 + .../public/services/theme_service.ts | 13 + .../public/services/ui_settings.ts | 13 + .../common/constants/base_formatters.ts | 2 + .../common/converters/currency.test.ts | 31 + .../common/converters/currency.ts | 23 + .../field_formats/common/converters/index.ts | 1 + src/plugins/field_formats/common/types.ts | 1 + .../public/__snapshots__/to_ast.test.ts.snap | 4 +- src/plugins/vis_types/metric/public/to_ast.ts | 2 +- .../page_objects/visualize_chart_page.ts | 2 +- .../page_objects/visualize_editor_page.ts | 2 +- .../services/dashboard/expectations.ts | 2 +- .../snapshots/baseline/combined_test3.json | 2 +- .../snapshots/baseline/final_output_test.json | 2 +- .../snapshots/baseline/metric_all_data.json | 2 +- .../snapshots/baseline/metric_empty_data.json | 2 +- .../baseline/metric_invalid_data.json | 2 +- .../baseline/metric_multi_metric_data.json | 2 +- .../baseline/metric_percentage_mode.json | 2 +- .../baseline/metric_single_metric_data.json | 2 +- .../snapshots/baseline/partial_test_2.json | 2 +- .../snapshots/baseline/step_output_test3.json | 2 +- .../snapshots/session/combined_test3.json | 2 +- .../snapshots/session/final_output_test.json | 2 +- .../snapshots/session/metric_all_data.json | 2 +- .../snapshots/session/metric_empty_data.json | 2 +- .../session/metric_multi_metric_data.json | 2 +- .../session/metric_percentage_mode.json | 2 +- .../session/metric_single_metric_data.json | 2 +- .../snapshots/session/partial_test_2.json | 2 +- .../snapshots/session/step_output_test3.json | 2 +- .../test_suites/run_pipeline/basic.ts | 4 +- .../test_suites/run_pipeline/metric.ts | 14 +- tsconfig.base.json | 2 + .../canvas_plugin_src/elements/index.ts | 3 +- .../elements/metric_vis/index.ts | 12 +- .../elements/metric_vis_legacy/index.ts | 21 + .../uis/models/metric_vis.ts | 2 +- .../i18n/elements/element_strings.test.ts | 4 +- .../canvas/i18n/elements/element_strings.ts | 10 +- .../visualization.test.ts | 2 +- .../metric_visualization/visualization.tsx | 2 +- .../translations/translations/fr-FR.json | 12 - .../translations/translations/ja-JP.json | 12 - .../translations/translations/zh-CN.json | 12 - .../time_to_visualize_security.ts | 2 +- .../apps/lens/group1/persistent_context.ts | 4 +- .../apps/lens/group2/add_to_dashboard.ts | 12 +- .../apps/lens/group2/epoch_millis.ts | 4 +- .../functional/apps/lens/group3/chart_data.ts | 2 +- .../functional/apps/lens/group3/formula.ts | 2 +- .../functional/apps/lens/group3/metrics.ts | 6 +- .../functional/apps/lens/group3/rollup.ts | 4 +- .../apps/lens/group3/tsvb_open_in_lens.ts | 2 +- .../data_visualizer/index_data_visualizer.ts | 4 +- 112 files changed, 2372 insertions(+), 343 deletions(-) create mode 100755 src/plugins/chart_expressions/expression_legacy_metric/.i18nrc.json create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/.storybook/main.js create mode 100755 src/plugins/chart_expressions/expression_legacy_metric/README.md create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/common/constants.ts rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap (99%) create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/index.ts rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/common/expression_functions/metric_vis_function.test.ts (100%) create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts create mode 100755 src/plugins/chart_expressions/expression_legacy_metric/common/index.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/common/types/index.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/jest.config.js create mode 100755 src/plugins/chart_expressions/expression_legacy_metric/kibana.json rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/__mocks__/format_service.ts (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/__mocks__/palette_service.ts (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/__mocks__/services.ts (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/__stories__/metric_renderer.stories.tsx (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/__snapshots__/metric_component.test.tsx.snap (99%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/__snapshots__/with_auto_scale.test.tsx.snap (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/metric.scss (81%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/metric_component.test.tsx (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/metric_component.tsx (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/metric_value.test.tsx (92%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/metric_value.tsx (88%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/with_auto_scale.styles.ts (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/with_auto_scale.test.tsx (100%) rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/components/with_auto_scale.tsx (100%) create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/expression_renderers/index.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/expression_renderers/metric_vis_renderer.tsx rename src/plugins/chart_expressions/{expression_metric => expression_legacy_metric}/public/format_service.ts (100%) create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/index.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/plugin.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/services/format_service.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/services/index.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/services/palette_service.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/utils/format.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/utils/index.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/public/utils/palette.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/server/index.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/server/plugin.ts create mode 100644 src/plugins/chart_expressions/expression_legacy_metric/tsconfig.json create mode 100644 src/plugins/chart_expressions/expression_metric/public/__mocks__/theme_service.ts create mode 100644 src/plugins/chart_expressions/expression_metric/public/components/currency_codes.test.ts create mode 100644 src/plugins/chart_expressions/expression_metric/public/components/currency_codes.ts create mode 100644 src/plugins/chart_expressions/expression_metric/public/components/metric_vis.test.tsx create mode 100644 src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx create mode 100644 src/plugins/chart_expressions/expression_metric/public/services/theme_service.ts create mode 100644 src/plugins/chart_expressions/expression_metric/public/services/ui_settings.ts create mode 100644 src/plugins/field_formats/common/converters/currency.test.ts create mode 100644 src/plugins/field_formats/common/converters/currency.ts create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/elements/metric_vis_legacy/index.ts diff --git a/.i18nrc.json b/.i18nrc.json index 412d16930c9ac..073a413fabf80 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -29,6 +29,7 @@ "expressionImage": "src/plugins/expression_image", "expressionMetric": "src/plugins/expression_metric", "expressionMetricVis": "src/plugins/chart_expressions/expression_metric", + "expressionLegacyMetricVis": "src/plugins/chart_expressions/expression_legacy_metric", "expressionPartitionVis": "src/plugins/chart_expressions/expression_partition_vis", "expressionXY": "src/plugins/chart_expressions/expression_xy", "expressionRepeatImage": "src/plugins/expression_repeat_image", @@ -57,10 +58,7 @@ "kibana-react": "src/plugins/kibana_react", "kibanaOverview": "src/plugins/kibana_overview", "lists": "packages/kbn-securitysolution-list-utils/src", - "management": [ - "src/legacy/core_plugins/management", - "src/plugins/management" - ], + "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "monaco": "packages/kbn-monaco/src", "navigation": "src/plugins/navigation", "newsfeed": "src/plugins/newsfeed", @@ -74,13 +72,8 @@ "sharedUXPackages": "packages/shared-ux", "coloring": "packages/kbn-coloring/src", "statusPage": "src/legacy/core_plugins/status_page", - "telemetry": [ - "src/plugins/telemetry", - "src/plugins/telemetry_management_section" - ], - "timelion": [ - "src/plugins/vis_types/timelion" - ], + "telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"], + "timelion": ["src/plugins/vis_types/timelion"], "uiActions": "src/plugins/ui_actions", "uiActionsEnhanced": "src/plugins/ui_actions_enhanced", "uiActionsExamples": "examples/ui_action_examples", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index c939ab2dcf690..041c0cee57359 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -114,6 +114,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |Expression Image plugin adds an image renderer to the expression plugin. The renderer will display the given image. +|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_legacy_metric/README.md[expressionLegacyMetricVis] +|Expression MetricVis plugin adds a metric renderer and function to the expression plugin. The renderer will display the metric chart. + + |{kib-repo}blob/{branch}/src/plugins/expression_metric/README.md[expressionMetric] |Expression Metric plugin adds a metric renderer and function to the expression plugin. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 0dafe3c51b77e..0dc1e1ee4675e 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -96,6 +96,7 @@ pageLoadAssetSize: securitySolution: 273763 customIntegrations: 28810 expressionMetricVis: 23121 + expressionLegacyMetricVis: 23121 expressionHeatmap: 27505 visTypeMetric: 23332 bfetch: 22837 diff --git a/src/plugins/chart_expressions/expression_legacy_metric/.i18nrc.json b/src/plugins/chart_expressions/expression_legacy_metric/.i18nrc.json new file mode 100755 index 0000000000000..28e2d09a1a433 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/.i18nrc.json @@ -0,0 +1,6 @@ +{ + "prefix": "expressionLegacyMetricVis", + "paths": { + "expressionLegacyMetricVis": "." + } +} diff --git a/src/plugins/chart_expressions/expression_legacy_metric/.storybook/main.js b/src/plugins/chart_expressions/expression_legacy_metric/.storybook/main.js new file mode 100644 index 0000000000000..80e65c9e371f0 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/.storybook/main.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { defaultConfig } from '@kbn/storybook'; +import webpackMerge from 'webpack-merge'; +import { resolve } from 'path'; + +const mockConfig = { + resolve: { + alias: { + '../../../expression_legacy_metric/public/services': resolve( + __dirname, + '../public/__mocks__/services.ts' + ), + }, + }, +}; + +module.exports = { + ...defaultConfig, + webpackFinal: (config) => webpackMerge(config, mockConfig), +}; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/README.md b/src/plugins/chart_expressions/expression_legacy_metric/README.md new file mode 100755 index 0000000000000..07b830feae67d --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/README.md @@ -0,0 +1,9 @@ +# expressionLegacyMetricVis + +Expression MetricVis plugin adds a `metric` renderer and function to the expression plugin. The renderer will display the `metric` chart. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/constants.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/constants.ts new file mode 100644 index 0000000000000..54cfe41f2a3b7 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/constants.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const EXPRESSION_METRIC_NAME = 'legacyMetricVis'; + +export const LabelPosition = { + BOTTOM: 'bottom', + TOP: 'top', +} as const; diff --git a/src/plugins/chart_expressions/expression_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap similarity index 99% rename from src/plugins/chart_expressions/expression_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap rename to src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap index 44cc3fee09b1f..9a7a7d5a5035c 100644 --- a/src/plugins/chart_expressions/expression_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/__snapshots__/metric_vis_function.test.ts.snap @@ -23,7 +23,7 @@ Object { exports[`interpreter/functions#metric returns an object with the correct structure 1`] = ` Object { - "as": "metricVis", + "as": "legacyMetricVis", "type": "render", "value": Object { "visConfig": Object { diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/index.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/index.ts new file mode 100644 index 0000000000000..5eccaa62fe464 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { metricVisFunction } from './metric_vis_function'; diff --git a/src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.test.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.test.ts similarity index 100% rename from src/plugins/chart_expressions/expression_metric/common/expression_functions/metric_vis_function.test.ts rename to src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.test.ts diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts new file mode 100644 index 0000000000000..8ec638d139bff --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/expression_functions/metric_vis_function.ts @@ -0,0 +1,206 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +import { + prepareLogTable, + Dimension, + validateAccessor, +} from '@kbn/visualizations-plugin/common/utils'; +import { ColorMode } from '@kbn/charts-plugin/common'; +import { visType } from '../types'; +import { MetricVisExpressionFunctionDefinition } from '../types'; +import { EXPRESSION_METRIC_NAME, LabelPosition } from '../constants'; + +const errors = { + severalMetricsAndColorFullBackgroundSpecifiedError: () => + i18n.translate( + 'expressionLegacyMetricVis.function.errors.severalMetricsAndColorFullBackgroundSpecified', + { + defaultMessage: + 'Full background coloring cannot be applied to a visualization with multiple metrics.', + } + ), + splitByBucketAndColorFullBackgroundSpecifiedError: () => + i18n.translate( + 'expressionLegacyMetricVis.function.errors.splitByBucketAndColorFullBackgroundSpecified', + { + defaultMessage: + 'Full background coloring cannot be applied to visualizations that have a bucket specified.', + } + ), +}; + +export const metricVisFunction = (): MetricVisExpressionFunctionDefinition => ({ + name: EXPRESSION_METRIC_NAME, + type: 'render', + inputTypes: ['datatable'], + help: i18n.translate('expressionLegacyMetricVis.function.help', { + defaultMessage: 'Metric visualization', + }), + args: { + percentageMode: { + types: ['boolean'], + default: false, + help: i18n.translate('expressionLegacyMetricVis.function.percentageMode.help', { + defaultMessage: 'Shows metric in percentage mode. Requires colorRange to be set.', + }), + }, + colorMode: { + types: ['string'], + default: `"${ColorMode.None}"`, + options: [ColorMode.None, ColorMode.Labels, ColorMode.Background], + help: i18n.translate('expressionLegacyMetricVis.function.colorMode.help', { + defaultMessage: 'Which part of metric to color', + }), + strict: true, + }, + colorFullBackground: { + types: ['boolean'], + default: false, + help: i18n.translate('expressionLegacyMetricVis.function.colorFullBackground.help', { + defaultMessage: 'Applies the selected background color to the full visualization container', + }), + }, + palette: { + types: ['palette'], + help: i18n.translate('expressionLegacyMetricVis.function.palette.help', { + defaultMessage: 'Provides colors for the values, based on the bounds.', + }), + }, + showLabels: { + types: ['boolean'], + default: true, + help: i18n.translate('expressionLegacyMetricVis.function.showLabels.help', { + defaultMessage: 'Shows labels under the metric values.', + }), + }, + font: { + types: ['style'], + help: i18n.translate('expressionLegacyMetricVis.function.font.help', { + defaultMessage: 'Font settings.', + }), + default: `{font size=60 align="center"}`, + }, + labelFont: { + types: ['style'], + help: i18n.translate('expressionLegacyMetricVis.function.labelFont.help', { + defaultMessage: 'Label font settings.', + }), + default: `{font size=24 align="center"}`, + }, + labelPosition: { + types: ['string'], + options: [LabelPosition.BOTTOM, LabelPosition.TOP], + help: i18n.translate('expressionLegacyMetricVis.function.labelPosition.help', { + defaultMessage: 'Label position', + }), + default: LabelPosition.BOTTOM, + strict: true, + }, + metric: { + types: ['string', 'vis_dimension'], + help: i18n.translate('expressionLegacyMetricVis.function.metric.help', { + defaultMessage: 'metric dimension configuration', + }), + required: true, + multi: true, + }, + bucket: { + types: ['string', 'vis_dimension'], + help: i18n.translate('expressionLegacyMetricVis.function.bucket.help', { + defaultMessage: 'bucket dimension configuration', + }), + }, + autoScale: { + types: ['boolean'], + help: i18n.translate('expressionLegacyMetricVis.function.autoScale.help', { + defaultMessage: 'Enable auto scale', + }), + required: false, + }, + }, + fn(input, args, handlers) { + if (args.percentageMode && !args.palette?.params) { + throw new Error('Palette must be provided when using percentageMode'); + } + + // currently we can allow colorize full container only for one metric + if (args.colorFullBackground) { + if (args.bucket) { + throw new Error(errors.splitByBucketAndColorFullBackgroundSpecifiedError()); + } + + if (args.metric.length > 1 || input.rows.length > 1) { + throw new Error(errors.severalMetricsAndColorFullBackgroundSpecifiedError()); + } + } + + args.metric.forEach((metric) => validateAccessor(metric, input.columns)); + validateAccessor(args.bucket, input.columns); + + if (handlers?.inspectorAdapters?.tables) { + handlers.inspectorAdapters.tables.reset(); + handlers.inspectorAdapters.tables.allowCsvExport = true; + + const argsTable: Dimension[] = [ + [ + args.metric, + i18n.translate('expressionLegacyMetricVis.function.dimension.metric', { + defaultMessage: 'Metric', + }), + ], + ]; + if (args.bucket) { + argsTable.push([ + [args.bucket], + i18n.translate('expressionLegacyMetricVis.function.dimension.splitGroup', { + defaultMessage: 'Split group', + }), + ]); + } + const logTable = prepareLogTable(input, argsTable, true); + handlers.inspectorAdapters.tables.logDatatable('default', logTable); + } + + return { + type: 'render', + as: EXPRESSION_METRIC_NAME, + value: { + visData: input, + visType, + visConfig: { + metric: { + palette: args.palette?.params, + percentageMode: args.percentageMode, + metricColorMode: args.colorMode, + labels: { + show: args.showLabels, + position: args.labelPosition, + style: { + ...args.labelFont, + }, + }, + colorFullBackground: args.colorFullBackground, + style: { + bgColor: args.colorMode === ColorMode.Background, + labelColor: args.colorMode === ColorMode.Labels, + ...args.font, + }, + autoScale: args.autoScale, + }, + dimensions: { + metrics: args.metric, + ...(args.bucket ? { bucket: args.bucket } : {}), + }, + }, + }, + }; + }, +}); diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/index.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/index.ts new file mode 100755 index 0000000000000..34cbdb6745312 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const PLUGIN_ID = 'expressionLegacyMetricVis'; +export const PLUGIN_NAME = 'expressionLegacyMetricVis'; + +export type { + MetricArguments, + MetricInput, + MetricVisRenderConfig, + MetricVisExpressionFunctionDefinition, + DimensionsVisParam, + MetricVisParam, + VisParams, + MetricOptions, +} from './types'; + +export { metricVisFunction } from './expression_functions'; + +export { EXPRESSION_METRIC_NAME } from './constants'; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts new file mode 100644 index 0000000000000..5ad540fc579f6 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_functions.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PaletteOutput } from '@kbn/coloring'; +import { + Datatable, + ExpressionFunctionDefinition, + ExpressionValueRender, + Style, +} from '@kbn/expressions-plugin'; +import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; +import { ColorMode, CustomPaletteState } from '@kbn/charts-plugin/common'; +import { VisParams, visType, LabelPositionType } from './expression_renderers'; +import { EXPRESSION_METRIC_NAME } from '../constants'; + +export interface MetricArguments { + percentageMode: boolean; + colorMode: ColorMode; + showLabels: boolean; + palette?: PaletteOutput; + font: Style; + labelFont: Style; + labelPosition: LabelPositionType; + metric: Array; + bucket?: ExpressionValueVisDimension | string; + colorFullBackground: boolean; + autoScale?: boolean; +} + +export type MetricInput = Datatable; + +export interface MetricVisRenderConfig { + visType: typeof visType; + visData: Datatable; + visConfig: Pick; +} + +export type MetricVisExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof EXPRESSION_METRIC_NAME, + MetricInput, + MetricArguments, + ExpressionValueRender +>; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.ts new file mode 100644 index 0000000000000..8c370480a7be9 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/types/expression_renderers.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { $Values } from '@kbn/utility-types'; +import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; +import { + ColorMode, + Labels, + CustomPaletteState, + Style as ChartStyle, +} from '@kbn/charts-plugin/common'; +import { Style } from '@kbn/expressions-plugin/common'; +import { LabelPosition } from '../constants'; + +export const visType = 'metric'; + +export interface DimensionsVisParam { + metrics: Array; + bucket?: ExpressionValueVisDimension | string; +} + +export type LabelPositionType = $Values; + +export type MetricStyle = Style & Pick; + +export type LabelsConfig = Labels & { style: Style; position: LabelPositionType }; +export interface MetricVisParam { + percentageMode: boolean; + percentageFormatPattern?: string; + metricColorMode: ColorMode; + palette?: CustomPaletteState; + labels: LabelsConfig; + style: MetricStyle; + colorFullBackground: boolean; + autoScale?: boolean; +} + +export interface VisParams { + addTooltip: boolean; + addLegend: boolean; + dimensions: DimensionsVisParam; + metric: MetricVisParam; + type: typeof visType; +} + +export interface MetricOptions { + value: string; + label: string; + color?: string; + bgColor?: string; + lightText: boolean; + colIndex: number; + rowIndex: number; +} diff --git a/src/plugins/chart_expressions/expression_legacy_metric/common/types/index.ts b/src/plugins/chart_expressions/expression_legacy_metric/common/types/index.ts new file mode 100644 index 0000000000000..9c50bfab1305d --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/common/types/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/jest.config.js b/src/plugins/chart_expressions/expression_legacy_metric/jest.config.js new file mode 100644 index 0000000000000..6b649ca8abadc --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/jest.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../', + roots: ['/src/plugins/chart_expressions/expression_legacy_metric'], + coverageDirectory: + '/target/kibana-coverage/jest/src/plugins/chart_expressions/expression_legacy_metric', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/plugins/chart_expressions/expression_legacy_metric/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/src/plugins/chart_expressions/expression_legacy_metric/kibana.json b/src/plugins/chart_expressions/expression_legacy_metric/kibana.json new file mode 100755 index 0000000000000..0c3489ddc55d1 --- /dev/null +++ b/src/plugins/chart_expressions/expression_legacy_metric/kibana.json @@ -0,0 +1,21 @@ +{ + "id": "expressionLegacyMetricVis", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + }, + "description": "Adds a `metric` renderer and function to the expression plugin. The renderer will display the `legacy metric` chart.", + "server": true, + "ui": true, + "requiredPlugins": [ + "expressions", + "fieldFormats", + "charts", + "visualizations", + "presentationUtil" + ], + "requiredBundles": ["kibanaUtils", "kibanaReact"], + "optionalPlugins": [] +} diff --git a/src/plugins/chart_expressions/expression_metric/public/__mocks__/format_service.ts b/src/plugins/chart_expressions/expression_legacy_metric/public/__mocks__/format_service.ts similarity index 100% rename from src/plugins/chart_expressions/expression_metric/public/__mocks__/format_service.ts rename to src/plugins/chart_expressions/expression_legacy_metric/public/__mocks__/format_service.ts diff --git a/src/plugins/chart_expressions/expression_metric/public/__mocks__/palette_service.ts b/src/plugins/chart_expressions/expression_legacy_metric/public/__mocks__/palette_service.ts similarity index 100% rename from src/plugins/chart_expressions/expression_metric/public/__mocks__/palette_service.ts rename to src/plugins/chart_expressions/expression_legacy_metric/public/__mocks__/palette_service.ts diff --git a/src/plugins/chart_expressions/expression_metric/public/__mocks__/services.ts b/src/plugins/chart_expressions/expression_legacy_metric/public/__mocks__/services.ts similarity index 100% rename from src/plugins/chart_expressions/expression_metric/public/__mocks__/services.ts rename to src/plugins/chart_expressions/expression_legacy_metric/public/__mocks__/services.ts diff --git a/src/plugins/chart_expressions/expression_metric/public/__stories__/metric_renderer.stories.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/__stories__/metric_renderer.stories.tsx similarity index 100% rename from src/plugins/chart_expressions/expression_metric/public/__stories__/metric_renderer.stories.tsx rename to src/plugins/chart_expressions/expression_legacy_metric/public/__stories__/metric_renderer.stories.tsx diff --git a/src/plugins/chart_expressions/expression_metric/public/components/__snapshots__/metric_component.test.tsx.snap b/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/metric_component.test.tsx.snap similarity index 99% rename from src/plugins/chart_expressions/expression_metric/public/components/__snapshots__/metric_component.test.tsx.snap rename to src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/metric_component.test.tsx.snap index ac950f3f7f2c4..106d45bc4a87c 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/__snapshots__/metric_component.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/metric_component.test.tsx.snap @@ -116,4 +116,4 @@ exports[`MetricVisComponent should render correct structure for single metric 1` } } /> -`; +`; \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap b/src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap similarity index 100% rename from src/plugins/chart_expressions/expression_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap rename to src/plugins/chart_expressions/expression_legacy_metric/public/components/__snapshots__/with_auto_scale.test.tsx.snap diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric.scss b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric.scss similarity index 81% rename from src/plugins/chart_expressions/expression_metric/public/components/metric.scss rename to src/plugins/chart_expressions/expression_legacy_metric/public/components/metric.scss index c99c191c57755..7adcb109bc931 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/metric.scss +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric.scss @@ -5,7 +5,7 @@ // mtrChart__legend--small // mtrChart__legend-isLoading -.mtrVis { +.legacyMtrVis { @include euiScrollBar; height: 100%; width: 100%; @@ -17,23 +17,23 @@ overflow: auto; } -.mtrVis__value { +.legacyMtrVis__value { @include euiTextTruncate; font-weight: $euiFontWeightBold; } -.mtrVis__container { +.legacyMtrVis__container { text-align: center; padding: $euiSize; display: flex; flex-direction: column; } -.mtrVis__container--light { +.legacyMtrVis__container--light { color: $euiColorEmptyShade; } -.mtrVis__container-isfull { +.legacyMtrVis__container-isfull { min-height: 100%; min-width: max-content; display: flex; @@ -43,13 +43,14 @@ flex: 1 0 100%; } -.mtrVis__container-isFilterable { +.legacyMtrVis__container-isFilterable { cursor: pointer; transition: transform $euiAnimSpeedNormal $euiAnimSlightResistance; transform: translate(0, 0); - &:hover, &:focus { + &:hover, + &:focus { box-shadow: none; transform: translate(0, -2px); } -} +} \ No newline at end of file diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_component.test.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.test.tsx similarity index 100% rename from src/plugins/chart_expressions/expression_metric/public/components/metric_component.test.tsx rename to src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.test.tsx diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx similarity index 100% rename from src/plugins/chart_expressions/expression_metric/public/components/metric_component.tsx rename to src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_component.tsx diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_value.test.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_value.test.tsx similarity index 92% rename from src/plugins/chart_expressions/expression_metric/public/components/metric_value.test.tsx rename to src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_value.test.tsx index fee24d8aa5e7f..0590faebe5f7d 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/metric_value.test.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_value.test.tsx @@ -75,7 +75,7 @@ describe('MetricVisValue', () => { /> ); component.simulate('click'); - expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(1); + expect(component.find('.legacyMtrVis__container-isfilterable')).toHaveLength(1); }); it('should not add -isfilterable class if onFilter is not provided', () => { @@ -88,7 +88,7 @@ describe('MetricVisValue', () => { /> ); component.simulate('click'); - expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(0); + expect(component.find('.legacyMtrVis__container-isfilterable')).toHaveLength(0); }); it('should call onFilter callback if provided', () => { @@ -116,6 +116,6 @@ describe('MetricVisValue', () => { labelConfig={labelConfig} /> ); - expect(component.find('.mtrVis__container-isfull').exists()).toBe(true); + expect(component.find('.legacyMtrVis__container-isfull').exists()).toBe(true); }); }); diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_value.tsx b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_value.tsx similarity index 88% rename from src/plugins/chart_expressions/expression_metric/public/components/metric_value.tsx rename to src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_value.tsx index 40ba0eb081564..1f9192aedc872 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/metric_value.tsx +++ b/src/plugins/chart_expressions/expression_legacy_metric/public/components/metric_value.tsx @@ -23,10 +23,10 @@ interface MetricVisValueProps { export const MetricVisValue = (props: MetricVisValueProps) => { const { style, metric, onFilter, labelConfig, colorFullBackground, autoScale } = props; - const containerClassName = classNames('mtrVis__container', { - 'mtrVis__container--light': metric.lightText, - 'mtrVis__container-isfilterable': onFilter, - 'mtrVis__container-isfull': !autoScale && colorFullBackground, + const containerClassName = classNames('legacyMtrVis__container', { + 'legacyMtrVis__container--light': metric.lightText, + 'legacyMtrVis__container-isfilterable': onFilter, + 'legacyMtrVis__container-isfull': !autoScale && colorFullBackground, }); useLayoutEffect(() => { @@ -41,7 +41,7 @@ export const MetricVisValue = (props: MetricVisValueProps) => { >
{