[ML] Hides filebeat configuration card for Serverless Search file upload (#178987)

Updates the text on the landing page

**Before**

![image](003a582f-4fe0-4d2a-a0dd-d40bd4ed57f5)

**After**

![image](75afae43-1855-4012-a728-488b6a3d3823)

Adds a new feature flag to allow the `Create filebeat configuration`
card to be hidden. This has been
[requested](https://github.com/elastic/enterprise-search-team/issues/7075)
for the serverless search project and so the `serverless.es.yml` has
been updated to include this flag.

**Before**

![image](add1aa39-186d-4816-9f92-27fa2f6f0378)

**After**


![image](a9ab7243-2fae-48fc-a3a0-0b2b347630be)
This commit is contained in:
James Gowdy 2024-04-12 16:52:07 +01:00 committed by GitHub
parent 5982f12238
commit ce14d99f3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 178 additions and 70 deletions

View file

@ -41,3 +41,5 @@ xpack.ml.ad.enabled: false
xpack.ml.dfa.enabled: false
xpack.ml.nlp.enabled: true
xpack.ml.compatibleModuleType: 'search'
data_visualizer.resultLinks.fileBeat.enabled: false

View file

@ -40,10 +40,10 @@ class TutorialDirectoryUi extends React.Component {
}),
content: <SampleDataTab />,
},
...extraTabs.map(({ id, name, component: Component }) => ({
...extraTabs.map(({ id, name, getComponent }) => ({
id,
name,
content: <Component />,
content: getComponent(),
})),
];

View file

@ -14,16 +14,16 @@ describe('AddDataService', () => {
test('allows multiple register directory header link calls', () => {
const setup = new AddDataService().setup();
expect(() => {
setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => <a>123</a> });
setup.registerAddDataTab({ id: 'def', name: 'a b c', component: () => <a>456</a> });
setup.registerAddDataTab({ id: 'abc', name: 'a b c', getComponent: () => <a>123</a> });
setup.registerAddDataTab({ id: 'def', name: 'a b c', getComponent: () => <a>456</a> });
}).not.toThrow();
});
test('throws when same directory header link is registered twice', () => {
const setup = new AddDataService().setup();
expect(() => {
setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => <a>123</a> });
setup.registerAddDataTab({ id: 'abc', name: 'a b c', component: () => <a>456</a> });
setup.registerAddDataTab({ id: 'abc', name: 'a b c', getComponent: () => <a>123</a> });
setup.registerAddDataTab({ id: 'abc', name: 'a b c', getComponent: () => <a>456</a> });
}).toThrow();
});
});
@ -38,8 +38,8 @@ describe('AddDataService', () => {
const service = new AddDataService();
const setup = service.setup();
const links = [
{ id: 'abc', name: 'a b c', component: () => <a>123</a> },
{ id: 'def', name: 'a b c', component: () => <a>456</a> },
{ id: 'abc', name: 'a b c', getComponent: () => <a>123</a> },
{ id: 'def', name: 'a b c', getComponent: () => <a>456</a> },
];
setup.registerAddDataTab(links[0]);
setup.registerAddDataTab(links[1]);

View file

@ -6,13 +6,11 @@
* Side Public License, v 1.
*/
import React from 'react';
/** @public */
export interface AddDataTab {
id: string;
name: string;
component: React.FC;
getComponent: () => JSX.Element;
}
export class AddDataService {

View file

@ -124,6 +124,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'data.search.sessions.maxUpdateRetries (number)',
'data.search.sessions.notTouchedTimeout (duration)',
'data_views.scriptedFieldsEnabled (any)', // It's a boolean (any because schema.conditional)
'data_visualizer.resultLinks.fileBeat.enabled (boolean)',
'dev_tools.deeplinks.navLinkStatus (string)',
'enterpriseSearch.canDeployEntSearch (boolean)',
'enterpriseSearch.host (string)',

View file

@ -0,0 +1,17 @@
/*
* 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 interface ResultLinks {
fileBeat?: {
enabled: boolean;
};
}
export type ResultLink = keyof ResultLinks;
export interface ConfigSchema {
resultLinks?: ResultLinks;
}

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { ResultLinks } from '../../common/app';
import { lazyLoadModules } from '../lazy_load_bundle';
import type {
DataDriftSpec,
@ -12,17 +13,31 @@ import type {
IndexDataVisualizerSpec,
} from '../application';
export async function getFileDataVisualizerComponent(): Promise<() => FileDataVisualizerSpec> {
const modules = await lazyLoadModules();
return () => modules.FileDataVisualizer;
export interface SpecWithLinks<T> {
resultLinks: ResultLinks;
component: T;
}
export async function getIndexDataVisualizerComponent(): Promise<() => IndexDataVisualizerSpec> {
const modules = await lazyLoadModules();
return () => modules.IndexDataVisualizer;
}
export function getComponents(resultLinks: ResultLinks) {
async function getFileDataVisualizerComponent(): Promise<
() => SpecWithLinks<FileDataVisualizerSpec>
> {
const modules = await lazyLoadModules(resultLinks);
return () => ({ component: modules.FileDataVisualizer, resultLinks });
}
export async function getDataDriftComponent(): Promise<() => DataDriftSpec> {
const modules = await lazyLoadModules();
return () => modules.DataDrift;
async function getIndexDataVisualizerComponent(): Promise<() => IndexDataVisualizerSpec> {
const modules = await lazyLoadModules(resultLinks);
return () => modules.IndexDataVisualizer;
}
async function getDataDriftComponent(): Promise<() => DataDriftSpec> {
const modules = await lazyLoadModules(resultLinks);
return () => modules.DataDrift;
}
return {
getFileDataVisualizerComponent,
getIndexDataVisualizerComponent,
getDataDriftComponent,
};
}

View file

@ -16,6 +16,7 @@ import type { FindFileStructureResponse } from '@kbn/file-upload-plugin/common';
import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public';
import { flatten } from 'lodash';
import { isDefined } from '@kbn/ml-is-defined';
import type { ResultLinks } from '../../../../../common/app';
import type { LinkCardProps } from '../link_card/link_card';
import { useDataVisualizerKibana } from '../../../kibana_context';
@ -50,6 +51,7 @@ interface Props {
createDataView: boolean;
showFilebeatFlyout(): void;
getAdditionalLinks?: GetAdditionalLinks;
resultLinks?: ResultLinks;
}
interface GlobalState {
@ -67,6 +69,7 @@ export const ResultsLinks: FC<Props> = ({
createDataView,
showFilebeatFlyout,
getAdditionalLinks,
resultLinks,
}) => {
const {
services: {
@ -257,21 +260,25 @@ export const ResultsLinks: FC<Props> = ({
/>
</EuiFlexItem>
)}
<EuiFlexItem>
<EuiCard
hasBorder
icon={<EuiIcon size="xxl" type={`filebeatApp`} />}
data-test-subj="fileDataVisFilebeatConfigLink"
title={
<FormattedMessage
id="xpack.dataVisualizer.file.resultsLinks.fileBeatConfig"
defaultMessage="Create Filebeat configuration"
/>
}
description=""
onClick={showFilebeatFlyout}
/>
</EuiFlexItem>
{resultLinks?.fileBeat?.enabled === false ? null : (
<EuiFlexItem>
<EuiCard
hasBorder
icon={<EuiIcon size="xxl" type={`filebeatApp`} />}
data-test-subj="fileDataVisFilebeatConfigLink"
title={
<FormattedMessage
id="xpack.dataVisualizer.file.resultsLinks.fileBeatConfig"
defaultMessage="Create Filebeat configuration"
/>
}
description=""
onClick={showFilebeatFlyout}
/>
</EuiFlexItem>
)}
{Array.isArray(asyncHrefCards) &&
asyncHrefCards.map((link) => (
<EuiFlexItem key={link.title}>

View file

@ -49,7 +49,7 @@ export const WelcomeContent: FC<Props> = ({ hasPermissionToImport }) => {
<h1>
<FormattedMessage
id="xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileTitle"
defaultMessage="Visualize data from a log file"
defaultMessage="Upload data from a file"
/>
</h1>
</EuiTitle>

View file

@ -142,10 +142,10 @@ export const FileContents: FC<Props> = ({ data, format, numberOfLines, semiStruc
) : (
<>
{highlightedLines.map((line, i) => (
<>
<React.Fragment key={`line-${i}`}>
{line}
{i === highlightedLines.length - 1 ? null : <EuiHorizontalRule margin="s" />}
</>
</React.Fragment>
))}
</>
)}

View file

@ -42,11 +42,15 @@ export function useGrokHighlighter() {
return lines.map((line) => {
const formattedWords: JSX.Element[] = [];
for (const { word, field } of line) {
for (let j = 0; j < line.length; j++) {
const { word, field } = line[j];
const key = `word-${j}`;
if (field) {
formattedWords.push(<FieldBadge type={field.type} value={word} name={field.name} />);
formattedWords.push(
<FieldBadge type={field.type} value={word} name={field.name} key={key} />
);
} else {
formattedWords.push(<span>{word}</span>);
formattedWords.push(<span key={key}>{word}</span>);
}
}
return (

View file

@ -345,6 +345,7 @@ export class FileDataVisualizerView extends Component {
dataStart={this.props.dataStart}
fileUpload={this.props.fileUpload}
getAdditionalLinks={this.props.getAdditionalLinks}
resultLinks={this.props.resultLinks}
capabilities={this.props.capabilities}
mode={mode}
onChangeMode={this.changeMode}

View file

@ -620,6 +620,7 @@ export class ImportView extends Component {
createDataView={createDataView}
showFilebeatFlyout={this.showFilebeatFlyout}
getAdditionalLinks={this.props.getAdditionalLinks ?? []}
resultLinks={this.props.resultLinks}
/>
{isFilebeatFlyoutVisible && (

View file

@ -8,18 +8,21 @@ import '../_index.scss';
import type { FC } from 'react';
import React from 'react';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import type { ResultLinks } from '../../../common/app';
import { getCoreStart, getPluginsStart } from '../../kibana_services';
// @ts-ignore
import { FileDataVisualizerView } from './components/file_data_visualizer_view';
import type { GetAdditionalLinks } from '../common/components/results_links';
interface Props {
export interface Props {
resultLinks?: ResultLinks;
getAdditionalLinks?: GetAdditionalLinks;
}
export type FileDataVisualizerSpec = typeof FileDataVisualizer;
export const FileDataVisualizer: FC<Props> = ({ getAdditionalLinks }) => {
export const FileDataVisualizer: FC<Props> = ({ getAdditionalLinks, resultLinks }) => {
const coreStart = getCoreStart();
const { data, maps, embeddable, discover, share, security, fileUpload, cloud, fieldFormats } =
getPluginsStart();
@ -48,6 +51,7 @@ export const FileDataVisualizer: FC<Props> = ({ getAdditionalLinks }) => {
http={coreStart.http}
fileUpload={fileUpload}
getAdditionalLinks={getAdditionalLinks}
resultLinks={resultLinks}
capabilities={coreStart.application.capabilities}
/>
</CloudContext>

View file

@ -273,7 +273,7 @@ const DataVisualizerStateContextProvider: FC<DataVisualizerStateContextProviderP
);
};
interface Props {
export interface Props {
getAdditionalLinks?: GetAdditionalLinks;
showFrozenDataTierChoice?: boolean;
esql?: boolean;

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import type { PluginInitializerContext } from '@kbn/core/public';
import { DataVisualizerPlugin } from './plugin';
export function plugin() {
return new DataVisualizerPlugin();
export function plugin(initializerContext: PluginInitializerContext) {
return new DataVisualizerPlugin(initializerContext);
}
export type { DataVisualizerPluginStart } from './plugin';

View file

@ -8,6 +8,7 @@
import type { FC } from 'react';
import React, { Suspense } from 'react';
import { EuiErrorBoundary, EuiSkeletonText } from '@elastic/eui';
import type { ResultLinks } from '../../common/app';
import type { DataDriftDetectionAppStateProps } from '../application/data_drift/data_drift_app_state';
const LazyWrapper: FC = ({ children }) => (
@ -20,14 +21,18 @@ const FileDataVisualizerComponent = React.lazy(
() => import('../application/file_data_visualizer/file_data_visualizer')
);
export const FileDataVisualizerWrapper: FC = () => {
export const FileDataVisualizerWrapper: FC<{ resultLinks?: ResultLinks }> = ({ resultLinks }) => {
return (
<React.Suspense fallback={<div />}>
<FileDataVisualizerComponent />
<FileDataVisualizerComponent resultLinks={resultLinks} />
</React.Suspense>
);
};
export function getFileDataVisualizerWrapper(resultLinks?: ResultLinks) {
return <FileDataVisualizerWrapper resultLinks={resultLinks} />;
}
const DataDriftLazy = React.lazy(() => import('../application/data_drift'));
/**

View file

@ -5,13 +5,12 @@
* 2.0.
*/
import type { HttpSetup } from '@kbn/core/public';
import type { ResultLinks } from '../../common/app';
import type {
DataDriftSpec,
FileDataVisualizerSpec,
IndexDataVisualizerSpec,
} from '../application';
import { getCoreStart } from '../kibana_services';
let loadModulesPromise: Promise<LazyLoadedModules>;
@ -19,10 +18,10 @@ interface LazyLoadedModules {
FileDataVisualizer: FileDataVisualizerSpec;
IndexDataVisualizer: IndexDataVisualizerSpec;
DataDrift: DataDriftSpec;
getHttp: () => HttpSetup;
resultsLinks: ResultLinks;
}
export async function lazyLoadModules(): Promise<LazyLoadedModules> {
export async function lazyLoadModules(resultsLinks: ResultLinks): Promise<LazyLoadedModules> {
if (typeof loadModulesPromise !== 'undefined') {
return loadModulesPromise;
}
@ -30,7 +29,7 @@ export async function lazyLoadModules(): Promise<LazyLoadedModules> {
loadModulesPromise = new Promise(async (resolve, reject) => {
try {
const lazyImports = await import('./lazy');
resolve({ ...lazyImports, getHttp: () => getCoreStart().http });
resolve({ ...lazyImports, resultsLinks });
} catch (error) {
reject(error);
}

View file

@ -5,7 +5,12 @@
* 2.0.
*/
import type { AnalyticsServiceStart, CoreSetup, CoreStart } from '@kbn/core/public';
import type {
AnalyticsServiceStart,
CoreSetup,
CoreStart,
PluginInitializerContext,
} from '@kbn/core/public';
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
import type { CloudStart } from '@kbn/cloud-plugin/public';
import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
@ -24,16 +29,13 @@ import type { LensPublicStart } from '@kbn/lens-plugin/public';
import type { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import {
getDataDriftComponent,
getFileDataVisualizerComponent,
getIndexDataVisualizerComponent,
} from './api';
import { getComponents } from './api';
import { getMaxBytesFormatted } from './application/common/util/get_max_bytes';
import { registerHomeAddData, registerHomeFeatureCatalogue } from './register_home';
import { registerEmbeddables } from './application/index_data_visualizer/embeddables';
import { setStartServices } from './kibana_services';
import { IndexDataVisualizerLocatorDefinition } from './application/index_data_visualizer/locator';
import type { ConfigSchema } from '../common/app';
export interface DataVisualizerSetupDependencies {
home?: HomePublicPluginSetup;
@ -72,12 +74,25 @@ export class DataVisualizerPlugin
DataVisualizerStartDependencies
>
{
private resultsLinks = {
fileBeat: {
enabled: true,
},
};
constructor(initializerContext: PluginInitializerContext<ConfigSchema>) {
const resultsLinks = initializerContext.config.get().resultLinks;
if (resultsLinks !== undefined) {
this.resultsLinks.fileBeat.enabled = resultsLinks.fileBeat?.enabled ?? true;
}
}
public setup(
core: CoreSetup<DataVisualizerStartDependencies, DataVisualizerPluginStart>,
plugins: DataVisualizerSetupDependencies
) {
if (plugins.home) {
registerHomeAddData(plugins.home);
registerHomeAddData(plugins.home, this.resultsLinks);
registerHomeFeatureCatalogue(plugins.home);
}
@ -87,6 +102,11 @@ export class DataVisualizerPlugin
public start(core: CoreStart, plugins: DataVisualizerStartDependencies) {
setStartServices(core, plugins);
const {
getFileDataVisualizerComponent,
getIndexDataVisualizerComponent,
getDataDriftComponent,
} = getComponents(this.resultsLinks);
return {
getFileDataVisualizerComponent,
getIndexDataVisualizerComponent,

View file

@ -7,21 +7,22 @@
import { i18n } from '@kbn/i18n';
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import { FileDataVisualizerWrapper } from './lazy_load_bundle/component_wrapper';
import { getFileDataVisualizerWrapper } from './lazy_load_bundle/component_wrapper';
import {
featureTitle,
FILE_DATA_VIS_TAB_ID,
applicationPath,
featureId,
} from '../common/constants';
import type { ResultLinks } from '../common/app';
export function registerHomeAddData(home: HomePublicPluginSetup) {
export function registerHomeAddData(home: HomePublicPluginSetup, resultsLinks: ResultLinks) {
home.addData.registerAddDataTab({
id: FILE_DATA_VIS_TAB_ID,
name: i18n.translate('xpack.dataVisualizer.file.embeddedTabTitle', {
defaultMessage: 'Upload file',
}),
component: FileDataVisualizerWrapper,
getComponent: () => getFileDataVisualizerWrapper(resultsLinks),
});
}

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { schema } from '@kbn/config-schema';
export const configSchema = schema.object({
resultLinks: schema.maybe(
schema.object({ fileBeat: schema.maybe(schema.object({ enabled: schema.boolean() })) })
),
});

View file

@ -5,9 +5,18 @@
* 2.0.
*/
import type { PluginInitializerContext } from '@kbn/core/server';
import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server';
import type { ConfigSchema } from '../common/app';
import { configSchema } from './config_schema';
export const plugin = async (initializerContext: PluginInitializerContext) => {
export const plugin = async (initializerContext: PluginInitializerContext<ConfigSchema>) => {
const { DataVisualizerPlugin } = await import('./plugin');
return new DataVisualizerPlugin(initializerContext);
};
export const config: PluginConfigDescriptor<ConfigSchema> = {
schema: configSchema,
exposeToBrowser: {
resultLinks: true,
},
};

View file

@ -15,16 +15,16 @@ import type {
import type { StartDeps, SetupDeps } from './types';
import { registerWithCustomIntegrations } from './register_custom_integration';
import { routes } from './routes';
import type { ConfigSchema } from '../common/app';
export class DataVisualizerPlugin implements Plugin {
private readonly _logger: Logger;
constructor(initializerContext: PluginInitializerContext) {
constructor(initializerContext: PluginInitializerContext<ConfigSchema>) {
this._logger = initializerContext.logger.get();
}
setup(coreSetup: CoreSetup<StartDeps, unknown>, plugins: SetupDeps) {
// home-plugin required
if (plugins.home && plugins.customIntegrations) {
registerWithCustomIntegrations(plugins.customIntegrations);
}

View file

@ -15,6 +15,7 @@ import type {
GetAdditionalLinks,
} from '@kbn/data-visualizer-plugin/public';
import { useTimefilter } from '@kbn/ml-date-picker';
import type { ResultLinks } from '@kbn/data-visualizer-plugin/common/app';
import { HelpMenu } from '../../components/help_menu';
import { useMlKibana, useMlLocator } from '../../contexts/kibana';
@ -39,6 +40,7 @@ export const FileDataVisualizerPage: FC = () => {
getMlNodeCount();
const [FileDataVisualizer, setFileDataVisualizer] = useState<FileDataVisualizerSpec | null>(null);
const [resultLinks, setResultLinks] = useState<ResultLinks | null>(null);
const getAdditionalLinks: GetAdditionalLinks = useMemo(
() => [
@ -100,10 +102,15 @@ export const FileDataVisualizerPage: FC = () => {
);
useEffect(() => {
// ML uses this function
if (dataVisualizer !== undefined) {
getMlNodeCount();
const { getFileDataVisualizerComponent } = dataVisualizer;
getFileDataVisualizerComponent().then(setFileDataVisualizer);
getFileDataVisualizerComponent().then((resp) => {
const items = resp();
setFileDataVisualizer(() => items.component);
setResultLinks(items.resultLinks);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -118,7 +125,10 @@ export const FileDataVisualizerPage: FC = () => {
defaultMessage="Data Visualizer"
/>
</MlPageHeader>
<FileDataVisualizer getAdditionalLinks={getAdditionalLinks} />
<FileDataVisualizer
getAdditionalLinks={getAdditionalLinks}
resultLinks={resultLinks ?? undefined}
/>
</>
) : null}
<HelpMenu docLink={docLinks.links.ml.guide} />