[8.x] [ML] Adds simple flyout based file upload to Search (#206864) (#208887)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ML] Adds simple flyout based file upload to Search
(#206864)](https://github.com/elastic/kibana/pull/206864)

<!--- Backport version: 9.6.4 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"James
Gowdy","email":"jgowdy@elastic.co"},"sourceCommit":{"committedDate":"2025-01-29T23:35:12Z","message":"[ML]
Adds simple flyout based file upload to Search (#206864)\n\nA minimal
version of the file upload tool which can be triggered via
a\r\nuiAction.\r\nThe trigger takes a callback to enable subsequent
actions after the\r\nupload. This callback receives information about
the upload, the index\r\nand data view created and information about the
files:\r\n```\r\n{\r\n \"index\": \"test9\",\r\n \"dataView\": {\r\n
\"id\": \"a870ef68-a624-4df1-9d5d-fa62b75dd297\",\r\n \"title\":
\"\"\r\n },\r\n \"files\": [\r\n {\r\n \"fileName\":
\"farequote-tiny.csv\",\r\n \"docCount\": 20,\r\n \"fileFormat\":
\"delimited\"\r\n },\r\n {\r\n \"fileName\": \"farequote.csv\",\r\n
\"docCount\": 86275,\r\n \"fileFormat\": \"delimited\"\r\n }\r\n
]\r\n}\r\n```\r\n\r\nIf `autoAddInference` is set with the name of an
inference endpoint\r\n(`autoAddInference: '.elser-2-elasticsearch'`) the
tool with\r\nautomatically add a `semantic_text` to the mappings for
tika files (pdf,\r\ntxt docx)\r\n\r\nCurrently embedded in the search
app's home page and playground. In\r\nplaygroubnd, after upload is
complete and the flyout closed, the newly\r\ncreated index will be
selected.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/0589fa02-fb0e-400b-8e74-1eb9a993c6ba\r\n\r\n\r\nMultiple
files can be uploaded at once. They must be of the same file\r\nformat
and the mappings cannot
clash.\r\n\r\n\r\n![image](a72408b0-7f33-4047-8351-90baa58d56a9)\r\n\r\n\r\nIf
more than once file has the same field but they are of
different\r\ntypes, the files are considered incompatible.\r\n\r\n<img
width=\"612\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/67307fd0-7d10-4eab-9e72-df133ebddcfe\"\r\n/>\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"70cd3cee7cb8cc643c97bb800f9863f8f77624d9","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement",":ml","Feature:File
and Index Data Viz","Feature:File
Upload","v9.0.0","backport:version","v8.18.0"],"title":"[ML] Adds simple
flyout based file upload to Search
","number":206864,"url":"https://github.com/elastic/kibana/pull/206864","mergeCommit":{"message":"[ML]
Adds simple flyout based file upload to Search (#206864)\n\nA minimal
version of the file upload tool which can be triggered via
a\r\nuiAction.\r\nThe trigger takes a callback to enable subsequent
actions after the\r\nupload. This callback receives information about
the upload, the index\r\nand data view created and information about the
files:\r\n```\r\n{\r\n \"index\": \"test9\",\r\n \"dataView\": {\r\n
\"id\": \"a870ef68-a624-4df1-9d5d-fa62b75dd297\",\r\n \"title\":
\"\"\r\n },\r\n \"files\": [\r\n {\r\n \"fileName\":
\"farequote-tiny.csv\",\r\n \"docCount\": 20,\r\n \"fileFormat\":
\"delimited\"\r\n },\r\n {\r\n \"fileName\": \"farequote.csv\",\r\n
\"docCount\": 86275,\r\n \"fileFormat\": \"delimited\"\r\n }\r\n
]\r\n}\r\n```\r\n\r\nIf `autoAddInference` is set with the name of an
inference endpoint\r\n(`autoAddInference: '.elser-2-elasticsearch'`) the
tool with\r\nautomatically add a `semantic_text` to the mappings for
tika files (pdf,\r\ntxt docx)\r\n\r\nCurrently embedded in the search
app's home page and playground. In\r\nplaygroubnd, after upload is
complete and the flyout closed, the newly\r\ncreated index will be
selected.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/0589fa02-fb0e-400b-8e74-1eb9a993c6ba\r\n\r\n\r\nMultiple
files can be uploaded at once. They must be of the same file\r\nformat
and the mappings cannot
clash.\r\n\r\n\r\n![image](a72408b0-7f33-4047-8351-90baa58d56a9)\r\n\r\n\r\nIf
more than once file has the same field but they are of
different\r\ntypes, the files are considered incompatible.\r\n\r\n<img
width=\"612\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/67307fd0-7d10-4eab-9e72-df133ebddcfe\"\r\n/>\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"70cd3cee7cb8cc643c97bb800f9863f8f77624d9"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/206864","number":206864,"mergeCommit":{"message":"[ML]
Adds simple flyout based file upload to Search (#206864)\n\nA minimal
version of the file upload tool which can be triggered via
a\r\nuiAction.\r\nThe trigger takes a callback to enable subsequent
actions after the\r\nupload. This callback receives information about
the upload, the index\r\nand data view created and information about the
files:\r\n```\r\n{\r\n \"index\": \"test9\",\r\n \"dataView\": {\r\n
\"id\": \"a870ef68-a624-4df1-9d5d-fa62b75dd297\",\r\n \"title\":
\"\"\r\n },\r\n \"files\": [\r\n {\r\n \"fileName\":
\"farequote-tiny.csv\",\r\n \"docCount\": 20,\r\n \"fileFormat\":
\"delimited\"\r\n },\r\n {\r\n \"fileName\": \"farequote.csv\",\r\n
\"docCount\": 86275,\r\n \"fileFormat\": \"delimited\"\r\n }\r\n
]\r\n}\r\n```\r\n\r\nIf `autoAddInference` is set with the name of an
inference endpoint\r\n(`autoAddInference: '.elser-2-elasticsearch'`) the
tool with\r\nautomatically add a `semantic_text` to the mappings for
tika files (pdf,\r\ntxt docx)\r\n\r\nCurrently embedded in the search
app's home page and playground. In\r\nplaygroubnd, after upload is
complete and the flyout closed, the newly\r\ncreated index will be
selected.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/0589fa02-fb0e-400b-8e74-1eb9a993c6ba\r\n\r\n\r\nMultiple
files can be uploaded at once. They must be of the same file\r\nformat
and the mappings cannot
clash.\r\n\r\n\r\n![image](a72408b0-7f33-4047-8351-90baa58d56a9)\r\n\r\n\r\nIf
more than once file has the same field but they are of
different\r\ntypes, the files are considered incompatible.\r\n\r\n<img
width=\"612\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/67307fd0-7d10-4eab-9e72-df133ebddcfe\"\r\n/>\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"70cd3cee7cb8cc643c97bb800f9863f8f77624d9"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2025-01-30 10:34:53 +00:00 committed by GitHub
parent 019de9a8a3
commit 958493f44f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 3034 additions and 31 deletions

1
.github/CODEOWNERS vendored
View file

@ -486,6 +486,7 @@ src/platform/plugins/shared/field_formats @elastic/kibana-data-discovery
src/platform/packages/shared/kbn-field-types @elastic/kibana-data-discovery
src/platform/packages/shared/kbn-field-utils @elastic/kibana-data-discovery
x-pack/platform/plugins/shared/fields_metadata @elastic/obs-ux-logs-team
x-pack/platform/packages/shared/file-upload-common @elastic/ml-ui
x-pack/platform/plugins/private/file_upload @elastic/kibana-presentation @elastic/ml-ui
examples/files_example @elastic/appex-sharedux
src/platform/plugins/private/files_management @elastic/appex-sharedux

View file

@ -535,6 +535,7 @@
"@kbn/field-types": "link:src/platform/packages/shared/kbn-field-types",
"@kbn/field-utils": "link:src/platform/packages/shared/kbn-field-utils",
"@kbn/fields-metadata-plugin": "link:x-pack/platform/plugins/shared/fields_metadata",
"@kbn/file-upload-common": "link:x-pack/platform/packages/shared/file-upload-common",
"@kbn/file-upload-plugin": "link:x-pack/platform/plugins/private/file_upload",
"@kbn/files-example-plugin": "link:examples/files_example",
"@kbn/files-management-plugin": "link:src/platform/plugins/private/files_management",

View file

@ -966,6 +966,8 @@
"@kbn/field-utils/*": ["src/platform/packages/shared/kbn-field-utils/*"],
"@kbn/fields-metadata-plugin": ["x-pack/platform/plugins/shared/fields_metadata"],
"@kbn/fields-metadata-plugin/*": ["x-pack/platform/plugins/shared/fields_metadata/*"],
"@kbn/file-upload-common": ["x-pack/platform/packages/shared/file-upload-common"],
"@kbn/file-upload-common/*": ["x-pack/platform/packages/shared/file-upload-common/*"],
"@kbn/file-upload-plugin": ["x-pack/platform/plugins/private/file_upload"],
"@kbn/file-upload-plugin/*": ["x-pack/platform/plugins/private/file_upload/*"],
"@kbn/files-example-plugin": ["examples/files_example"],

View file

@ -0,0 +1,3 @@
# @kbn/file-upload-common
Types and constants used by the file upload tool and other plugins which use it.

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export type { FileUploadResults, OpenFileUploadLiteContext } from './src/types';
export { OPEN_FILE_UPLOAD_LITE_ACTION, OPEN_FILE_UPLOAD_LITE_TRIGGER } from './src/constants';

View file

@ -0,0 +1,12 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/platform/packages/shared/file-upload-common'],
};

View file

@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/file-upload-common",
"owner": "@elastic/ml-ui",
"group": "platform",
"visibility": "shared"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/file-upload-common",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -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 const OPEN_FILE_UPLOAD_LITE_ACTION = 'openFileUploadLiteTrigger';
export const OPEN_FILE_UPLOAD_LITE_TRIGGER = 'OPEN_FILE_UPLOAD_LITE_TRIGGER';

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { IndicesIndexSettings } from '@elastic/elasticsearch/lib/api/types';
export interface FileUploadResults {
index: string;
pipelineId?: string;
dataView?: { id: string; title: string };
inferenceId?: string;
files: Array<{ fileName: string; docCount: number; fileFormat: string; documentType: string }>;
}
export interface OpenFileUploadLiteContext {
onUploadComplete?: (results: FileUploadResults | null) => void;
indexSettings?: IndicesIndexSettings;
autoAddInference?: string;
}

View file

@ -0,0 +1,17 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node"
]
},
"include": [
"**/*.ts",
],
"exclude": [
"target/**/*"
],
"kbn_references": []
}

View file

@ -31,6 +31,15 @@ export const FILE_FORMATS = {
TIKA: 'tika',
};
export function isSupportedFormat(format: string) {
return (
format === FILE_FORMATS.NDJSON ||
format === FILE_FORMATS.DELIMITED ||
format === FILE_FORMATS.SEMI_STRUCTURED_TEXT ||
format === FILE_FORMATS.TIKA
);
}
export const SUPPORTED_FIELD_TYPES = {
BOOLEAN: 'boolean',
CONFLICT: 'conflict',

View file

@ -366,6 +366,7 @@ export class FileDataVisualizerView extends Component {
mode={mode}
onChangeMode={this.changeMode}
onCancel={this.onCancel}
setUploadResults={this.props.setUploadResults}
/>
</>
)}

View file

@ -31,6 +31,7 @@ export async function analyzeTikaFile(
need_client_timezone: false,
num_lines_analyzed: numLinesAnalyzed,
num_messages_analyzed: 0,
explanation: [],
field_stats: {
// @ts-expect-error semantic_text not supported
'attachment.content': {},

View file

@ -159,6 +159,7 @@ export async function importData(props: Props, config: Config, setState: (state:
await autoDeploy.deploy();
setState({
initializeDeploymentStatus: IMPORT_STATUS.COMPLETE,
inferenceId,
});
} catch (error) {
success = false;
@ -179,6 +180,15 @@ export async function importData(props: Props, config: Config, setState: (state:
const initializeImportResp = await importer.initializeImport(index, settings, mappings, pipeline);
if (initializeImportResp.success === false) {
errors.push(initializeImportResp.error);
setState({
initializeImportStatus: IMPORT_STATUS.FAILED,
errors,
});
return;
}
const timeFieldName = importer.getTimeField();
setState({ timeFieldName });
@ -249,7 +259,7 @@ export async function importData(props: Props, config: Config, setState: (state:
});
}
async function createKibanaDataView(
export async function createKibanaDataView(
dataViewName: string,
dataViewsContract: DataViewsServicePublic,
timeFieldName?: string
@ -276,7 +286,7 @@ function getSuccess(success: boolean) {
return success ? IMPORT_STATUS.COMPLETE : IMPORT_STATUS.FAILED;
}
function getInferenceId(mappings: MappingTypeMapping) {
export function getInferenceId(mappings: MappingTypeMapping) {
for (const value of Object.values(mappings.properties ?? {})) {
if (value.type === 'semantic_text') {
return value.inference_id;

View file

@ -79,6 +79,7 @@ const DEFAULT_STATE = {
createPipeline: true,
initializeDeployment: false,
initializeDeploymentStatus: IMPORT_STATUS.INCOMPLETE,
inferenceId: null,
};
export class ImportView extends Component {

View file

@ -9,6 +9,7 @@ import type { FC, PropsWithChildren } from 'react';
import React from 'react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { FileUploadResults } from '@kbn/file-upload-common';
import type { ResultLinks } from '../../../common/app';
import { getCoreStart, getPluginsStart } from '../../kibana_services';
@ -19,11 +20,16 @@ import type { GetAdditionalLinks } from '../common/components/results_links';
export interface Props {
resultLinks?: ResultLinks;
getAdditionalLinks?: GetAdditionalLinks;
setUploadResults?: (results: FileUploadResults) => void;
}
export type FileDataVisualizerSpec = typeof FileDataVisualizer;
export const FileDataVisualizer: FC<Props> = ({ getAdditionalLinks, resultLinks }) => {
export const FileDataVisualizer: FC<Props> = ({
getAdditionalLinks,
resultLinks,
setUploadResults,
}) => {
const coreStart = getCoreStart();
const { data, maps, embeddable, share, fileUpload, cloud, fieldFormats } = getPluginsStart();
const services = {
@ -50,6 +56,7 @@ export const FileDataVisualizer: FC<Props> = ({ getAdditionalLinks, resultLinks
getAdditionalLinks={getAdditionalLinks}
resultLinks={resultLinks}
capabilities={coreStart.application.capabilities}
setUploadResults={setUploadResults}
/>
</CloudContext>
</KibanaContextProvider>

View file

@ -8,6 +8,7 @@
import type { FC, PropsWithChildren } from 'react';
import React, { Suspense } from 'react';
import { EuiErrorBoundary, EuiSkeletonText } from '@elastic/eui';
import type { FileUploadResults } from '@kbn/file-upload-common';
import type { ResultLinks } from '../../common/app';
import type { DataDriftDetectionAppStateProps } from '../application/data_drift/data_drift_app_state';
@ -21,16 +22,24 @@ const FileDataVisualizerComponent = React.lazy(
() => import('../application/file_data_visualizer/file_data_visualizer')
);
export const FileDataVisualizerWrapper: FC<{ resultLinks?: ResultLinks }> = ({ resultLinks }) => {
export const FileDataVisualizerWrapper: FC<{
resultLinks?: ResultLinks;
setUploadResults?: (results: FileUploadResults) => void;
}> = ({ resultLinks, setUploadResults }) => {
return (
<React.Suspense fallback={<div />}>
<FileDataVisualizerComponent resultLinks={resultLinks} />
<FileDataVisualizerComponent resultLinks={resultLinks} setUploadResults={setUploadResults} />
</React.Suspense>
);
};
export function getFileDataVisualizerWrapper(resultLinks?: ResultLinks) {
return <FileDataVisualizerWrapper resultLinks={resultLinks} />;
export function getFileDataVisualizerWrapper(
resultLinks?: ResultLinks,
setUploadResults?: (results: FileUploadResults) => void
) {
return (
<FileDataVisualizerWrapper resultLinks={resultLinks} setUploadResults={setUploadResults} />
);
}
const DataDriftLazy = React.lazy(() => import('../application/data_drift'));

View file

@ -0,0 +1,551 @@
/*
* 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 React from 'react';
import { useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/css';
export const DataViewIllustration = () => {
const { euiTheme } = useEuiTheme();
const { colors } = euiTheme;
const dataViewIllustrationVerticalStripes = css`
fill: ${colors.fullShade};
`;
const dataViewIllustrationDots = css`
fill: ${colors.lightShade};
`;
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="226"
height="206"
fill="none"
viewBox="0 0 226 206"
>
<g id="ilustration">
<g id="database-bottom">
<path
id="rectangle"
fill="#1BA9F5"
d="M136.167 130.535H15.677c-5.667 0-10.26 4.594-10.26 10.26v25.26c0 5.667 4.593 10.26 10.26 10.26h120.49c5.666 0 10.26-4.593 10.26-10.26v-25.26c0-5.666-4.594-10.26-10.26-10.26z"
/>
<g id="database-bottom-dots" fill="#0D90E0">
<path
id="Vector"
d="M6.017 147.855l.08-.07a1.099 1.099 0 00-.5-1.48l-.18-.05v2.23a2.55 2.55 0 00.6-.63z"
/>
<path d="M15.247 173.515a1.798 1.798 0 00-2.12-.92c-.334.22-.575.555-.68.94a.644.644 0 00-.42-.25c.12-.227.175-.483.16-.74a1.147 1.147 0 00-.18-.53 1.46 1.46 0 001-.21c.5-.28.8-1 .58-1.37a1.532 1.532 0 00-1.73-.59.904.904 0 00-.19.15c.005-.09.005-.18 0-.27a.458.458 0 00.22-.6c-.09-.29-.19-1-.69-.94a.482.482 0 00-.32.15 5.339 5.339 0 00-.52-.38 1.243 1.243 0 01-.46-.51 1.07 1.07 0 00-1.68-.61.563.563 0 00-.1-.3.942.942 0 00-1.21-.08 1.001 1.001 0 00-.44 1.31.75.75 0 00.44.31.8.8 0 00-.06.36 1.138 1.138 0 001.16 1.17 1.63 1.63 0 00.5-.16c.023.085.04.172.05.26 0 .66.55 1 1 1.31.266.124.563.166.853.121.29-.044.56-.173.777-.371l.11-.11a1.41 1.41 0 00.09.91 2.056 2.056 0 00-1.1-.16 1.228 1.228 0 00-1.1.78.7.7 0 00-.59.07 2.1 2.1 0 00-.66.57c.448.5.94.959 1.47 1.37a.997.997 0 00.25-.41 1.588 1.588 0 001.25.39c.116-.022.227-.062.33-.12a.59.59 0 00.49.55.732.732 0 00.62-.11.92.92 0 00.13.34 1.719 1.719 0 002.15.46 1.359 1.359 0 00.62-1.78z" />
<path d="M13.287 175.915a.647.647 0 00-.27 0l.58.15a.478.478 0 00-.31-.15z" />
<path d="M5.417 162.055v2c.13.037.27.037.4 0a1.126 1.126 0 00.551-.712 1.121 1.121 0 00-.151-.888.775.775 0 00-.8-.4z" />
<path d="M8.527 171.115a.916.916 0 00-1.23-.3c-.07.055-.134.119-.19.19a.752.752 0 00-.15 0 1.54 1.54 0 00-.23.07c.338.602.736 1.169 1.19 1.69a2.12 2.12 0 00.42-.57.831.831 0 00.19-1.08z" />
<path d="M6.827 162.085a1.828 1.828 0 00.09 1.51c.24.55 0 1.13.46 1.53a2.604 2.604 0 002.32.64.589.589 0 000 .86c.23.23.62.38.84 0a.939.939 0 000-.86.831.831 0 00-.56-.16c.108-.036.211-.083.31-.14a2 2 0 001.11-1.58c.06-.7-.78-1-1-1.74a1.86 1.86 0 00-.82-1.13c.18-.21.28-.36.27-.51a2.87 2.87 0 00.2-.36c-.32-.35-.56-.88-1.2-.61a.787.787 0 00-.48 1c.044.13.12.247.22.34a3.48 3.48 0 00-1.06.35 1.801 1.801 0 00-.7.86z" />
<path d="M11.877 166.235a.831.831 0 00.9-.8.684.684 0 00.8-.33 1 1 0 00.08-.79c-.31-.57-.82-.5-1.43-.22v.47l-.21-.08a.94.94 0 00-.77.42 1 1 0 00.63 1.33z" />
<path d="M5.787 164.465a.88.88 0 00-.37-.34v2.47a1.65 1.65 0 00.28-.11c.55-.29.48-1.27.09-2.02z" />
<path d="M15.637 173.315a.55.55 0 00.38-.43c0-.15-.19-.59-.4-.55a.549.549 0 00-.31.49c.03.17.13.53.33.49z" />
<path d="M6.387 165.995a.532.532 0 00.57 0c.12-.09.32-.54.15-.66a.552.552 0 00-.58.09c-.11.18-.3.48-.14.57z" />
<path d="M13.227 138.575a1.45 1.45 0 00.24 1.71 1.457 1.457 0 001.52-.28 1.002 1.002 0 00-.11-1.2c-.65-.54-1.31-.63-1.65-.23z" />
<path d="M15.117 142.065a1.306 1.306 0 00-.78.09 1.723 1.723 0 00-.86 1.24 1.271 1.271 0 00.6 1.22 1.094 1.094 0 001.12 0 1.34 1.34 0 00.72-1c0-.46.16-1-.12-1.25a.815.815 0 00-.68-.3z" />
<path d="M16.737 146.405c-.62.34-.52.75-.93.93-.41.18-.45.49-.36 1 .09.51.24 1.2.9 1.05.66-.15.66-.59.6-.79-.06-.2.14-.58.28-.39.14.19.42.23.89-.11a.859.859 0 00.454-.536.848.848 0 00-.104-.694 1.26 1.26 0 00-1.73-.46z" />
<path d="M18.867 150.895a.738.738 0 00-1.16.51.776.776 0 00.74.66c.42-.07.52-.3.42-1.17z" />
<path d="M19.237 139.995c-.5-.42-1.06-.31-1.59.33a1.153 1.153 0 00.08 1.39 1.905 1.905 0 001.92 0 1.418 1.418 0 00-.41-1.72z" />
<path d="M16.127 164.725c.13-.09.31-.33.22-.53s-.4-.2-.56-.15c-.16.05-.44.35-.29.59.15.24.53.16.63.09z" />
<path d="M12.147 155.155a.763.763 0 001-.12c.26-.34.13-.59-.59-1.09a.73.73 0 00-.41 1.21z" />
<path d="M13.307 151.585a.674.674 0 00.23-.05.547.547 0 00.32.1c.16 0 .43-.08.42-.35a.27.27 0 00-.2-.29c.1-.48-.23-.82-.7-1.07-.44.16-.9.37-.87.94a.728.728 0 00.501.686.72.72 0 00.299.034z" />
<path d="M16.737 170.215h.06c.16 0 .52-.1.58-.31.06-.21-.09-.23-.21-.3-.12-.07 0-.1 0-.16.07-.8-.14-1.13-.85-1.2.222-.466.345-.974.36-1.49a1.246 1.246 0 00-.12-.43 1.048 1.048 0 00-.872-.594 1.057 1.057 0 00-.948.464 6.609 6.609 0 00-.63 1.31c-.09-.62-.33-.65-1.14-.44-.25.6 0 1 .58 1.24-.28 1.52.45 2 2 1.5.069.058.132.121.19.19.26.4.6.37 1 .22z" />
<path d="M39.377 170.995c.52.38 1 .24 1.34-.26 1.41.64 2.08.06 2-1.58.079-.068.166-.125.26-.17.43-.17.48-.5.44-.9a.788.788 0 00-.63-.67c-.84-.3-1.21-.11-1.45.76-.18.65-.39 1.29-.59 1.93-1.26-.23-1.37-.11-1.37.89z" />
<path d="M39.737 174.855c-.44-.07-1.28.81-1.33 1.37v.09h2.23a1.38 1.38 0 00-.9-1.46z" />
<path d="M42.047 165.145a.715.715 0 00-.257-.482.701.701 0 00-.245-.131.727.727 0 00-.278-.027.999.999 0 00-.7.39c-.29.57.06 1 .63 1.3.45-.28.97-.43.85-1.05z" />
<path d="M51.597 174.475a.993.993 0 00.865-.755c.032-.133.038-.27.015-.405a1.59 1.59 0 00-.8-1 .55.55 0 00-.4-.23.858.858 0 00-.93.61v.06a1.317 1.317 0 00-.29 1c.09.44.91.84 1.54.72z" />
<path d="M30.187 175.625a1.192 1.192 0 00-1.275-.671c-.17.028-.332.094-.475.191a.815.815 0 00-.17.15.839.839 0 00-.24-.07c-.32 0-.67 0-.78.38a.782.782 0 00.26.71h2.82a1.13 1.13 0 00-.14-.69z" />
<path d="M20.737 145.325c.31.49.49 1.12 1.17 1.35l.75-.31c.66.38 1.4.43 1.67.08a.771.771 0 000-1c-.41-.56-.7-.62-1.47-.31a12.3 12.3 0 00-1.46-.62.544.544 0 00-.623.166.55.55 0 00-.037.644z" />
<path d="M27.637 168.845a.769.769 0 00.12-1c-.26-.32-.54-.26-1.2.31a.73.73 0 001.08.69z" />
<path d="M22.627 142.365c.14.16.53-.06.56-.15a.81.81 0 000-.55.562.562 0 00-.45 0c-.13.18-.26.53-.11.7z" />
<path d="M57.527 168.335c0-.15-.27-.49-.46-.49s-.32.48-.28.62c.04.14.29.38.45.34a.537.537 0 00.29-.47z" />
<path d="M26.387 164.575c-.12.07-.12.35-.19.63.28 0 .52.1.65 0a.503.503 0 00.07-.5.564.564 0 00-.53-.13z" />
<path d="M56.247 171.995a1.204 1.204 0 00-1.04 1.023 1.2 1.2 0 00.04.517c.18.64.78 1.08 1.25.93a1.69 1.69 0 001.08-1.82 1.178 1.178 0 00-1.33-.65z" />
<path d="M20.867 155.185a.76.76 0 00.73.65.706.706 0 00.564-.451.698.698 0 00-.134-.709.732.732 0 00-1.024.156.731.731 0 00-.136.354z" />
<path d="M22.017 150.455a.297.297 0 00.107-.23.302.302 0 00-.107-.23c-.12-.11-.45-.36-.63-.2-.18.16 0 .52.07.57.07.05.36.27.56.09z" />
<path d="M57.447 176.315h2.55a2.101 2.101 0 000-.55 1.41 1.41 0 00-1.52-1.12c-.63.09-1.07.88-1.03 1.67z" />
<path d="M76.157 173.505c.3-.38-.44-1.27-1-1.19-.56.08-1 .66-.84 1.05.16.39 1.54.53 1.84.14z" />
<path d="M66.077 174.755c.39-.09.88-.75.79-1a1.19 1.19 0 00-1.23-.7.839.839 0 00-.53 1.05.928.928 0 00.97.65z" />
<path d="M58.297 170.785c.21 0 .24-.45.21-.61-.03-.16-.25-.18-.39-.26-.09.16-.24.32-.25.49-.01.17.23.42.43.38z" />
<path d="M69.447 175.995c-.08-.48-1-1.19-1.44-1.09-.61.14-.85.63-.66 1.37h2.09a.57.57 0 00.01-.28z" />
<path d="M74.147 174.775a.763.763 0 00-.47-.28c-.06 0-.26.31-.23.4.03.09.29.45.51.42.22-.03.24-.47.19-.54z" />
<path d="M68.237 172.425a.744.744 0 00.61-.86.647.647 0 00-.57-.51.647.647 0 00-.27.03.596.596 0 00-.493.478.601.601 0 00.003.242.64.64 0 00.72.62z" />
<path d="M5.557 151.105a.655.655 0 00-.14 0v3a2.187 2.187 0 001.1-1.28 1.501 1.501 0 00-.96-1.72z" />
<path d="M47.057 175.235c-.09.6.14 1 .66 1 .73.12 1.57-.35 1.64-.92a1.278 1.278 0 00-1.11-1.16 1.16 1.16 0 00-1.19 1.08z" />
<path d="M47.017 171.155a.463.463 0 00-.408.073.461.461 0 00-.192.367.463.463 0 00.282.487.46.46 0 00.198.033.51.51 0 00.45-.45.44.44 0 00-.33-.51z" />
<path d="M45.737 169.445c-.14 0-.62 0-.63.25a.536.536 0 00.4.42c.18 0 .54 0 .55-.2a.54.54 0 00-.32-.47z" />
<path d="M48.807 171.275c.072.088.153.168.24.24.06-.08.19-.12.18-.25-.01-.13-.18-.17-.26-.17-.08 0-.17.16-.16.18z" />
<path d="M45.927 174.995c-.13.19-.38.39-.37.57.01.18.13.7.55.61.42-.09.38-.58.34-.75-.04-.17-.34-.25-.52-.43z" />
<path d="M60.847 176.315h.79a.465.465 0 00-.34-.21.765.765 0 00-.45.21z" />
<path d="M61.347 174.515c.12.49.78 1 1.19.93.41-.07.79-.82.63-1.54a.623.623 0 00-.552-.523.628.628 0 00-.268.033c-.61.1-1.1.67-1 1.1z" />
<path d="M44.857 171.895a.568.568 0 00-.29.47c0 .13.3.21.55.36.09-.26.24-.47.2-.62a.518.518 0 00-.46-.21z" />
<path d="M54.607 175.535a1.4 1.4 0 00-.33.78h2.22a1.35 1.35 0 00-.14-.36c-.21-.36-.43-1.21-1-1.2-.57.01-.56.56-.75.78z" />
<path d="M41.657 173.265c.09-.45-.1-.77-.53-.74-.43.03-1-.06-1.09.44-.09.5.57.71.86.86.29.15.67-.14.76-.56z" />
<path d="M49.487 169.425c0-.07-.08-.25-.17-.33-.08-.48-.74-.53-1-.75l-.24.13v1.32l.3.18a2.01 2.01 0 01.46-.25.417.417 0 00.22.19c.29.08.37-.33.43-.49z" />
<path d="M62.977 168.245c-.16.38-.73.8-.49 1.18a.767.767 0 00.59.365.759.759 0 00.64-.265c.13-.2-.16-.7-.3-1.05-.04-.09-.23-.12-.44-.23z" />
<path d="M27.887 163.435a.733.733 0 00.345.65.726.726 0 00.735.02.76.76 0 00.12-1 .708.708 0 00-.698-.19.716.716 0 00-.502.52z" />
<path d="M24.457 141.595c-.23.35.42 1.55.9 1.58.48.03.81-1.08.45-1.48-.36-.4-1.07-.46-1.35-.1z" />
<path d="M77.477 175.695c-.55 0-.8.16-1 .62h1.75v-.09a.757.757 0 00-.75-.53z" />
<path d="M44.257 166.095c-.27 0-.7.26-.74.46-.04.2.22.57.31.77.62.1.81-.18.87-.5.06-.32-.04-.74-.44-.73z" />
<path d="M32.657 164.435a1.106 1.106 0 00-1.24.5 5.358 5.358 0 00-.48 1 4.828 4.828 0 00-.52.1 1.65 1.65 0 00-1.23 1.38 1.891 1.891 0 00.95 1.58.931.931 0 00.72.09 1.503 1.503 0 00.935-.684 1.51 1.51 0 00.175-1.146l-.09-.54c.34-.05.67-.08 1-.16.6-.16.78-.43.71-1a1.261 1.261 0 00-.93-1.12z" />
<path d="M42.947 174.595a.655.655 0 000 .14.512.512 0 00-.28 0c-.6.08-.71.53-.67 1.06a1.154 1.154 0 001.41.08c.172.133.375.219.59.25a1.639 1.639 0 001.79-1.41 1.695 1.695 0 00-1.29-1.56c-.76-.16-1.41.46-1.55 1.44z" />
<path d="M37.567 175.815c-.15.05-.24.28-.27.5h1c-.14-.24-.56-.58-.73-.5z" />
<path d="M37.337 164.305c0-.14-.19-.41-.33-.45-.14-.04-.6.06-.63.25-.03.19.33.47.47.49a.68.68 0 00.49-.29z" />
<path d="M34.537 168.435a1 1 0 00.91 1.14c.63.15 1.54-.26 1.65-.74a1.47 1.47 0 00-1-1.63 1.488 1.488 0 00-1.56 1.23z" />
<path d="M32.437 174.745a1.7 1.7 0 00.61.95 1.848 1.848 0 001.49.29c.6-.1 1.1.26 1.6-.07a2.508 2.508 0 001.1-2.8 1.999 1.999 0 00-1.26-1.47c-.67-.23-1.2.51-1.93.55-1.74.1-1.89 1.4-1.61 2.55z" />
<path d="M28.667 168.315a1.421 1.421 0 00-.16.3c.09 0 .17.15.29.09.12-.06.11-.22.09-.3-.02-.08-.15-.11-.22-.09z" />
<path d="M8.737 156.915a2.064 2.064 0 001.93-.21c.56-.42.25-1.27.63-1.9.9-1.49-.08-2.36-1.18-2.77a2.12 2.12 0 00-.4-.1.92.92 0 00-.55-.34 1.26 1.26 0 00-1.47 1 1.34 1.34 0 000 .71c-.28.37-.67.61-.68 1.11a2.509 2.509 0 001.72 2.5z" />
<path d="M5.417 141.485v2.76c.183-.085.344-.212.47-.37.72-1 .55-1.32-.22-2.19a1.219 1.219 0 00-.25-.2z" />
<path d="M6.237 156.435c-.106.081-.2.175-.28.28a.344.344 0 00-.09-.12 1.239 1.239 0 00-.32-.5.772.772 0 00-.13-.06v2.17h.06c-.019.216.02.433.11.63.164.278.43.482.74.57l-.22.13c-.32.17-.27.64 0 .95.27.31.69.36.91 0 .22-.36.53-.7.33-1a1.56 1.56 0 00.35-.16c.23-.15.412-.366.52-.62l.05.06a.745.745 0 001.08-.12 1.002 1.002 0 00.15-.86.87.87 0 00-1.13-.36 1.492 1.492 0 00-.1-.22 1.613 1.613 0 00-2.03-.77z" />
<path d="M7.737 142.505a1.138 1.138 0 00.17.88 1.5 1.5 0 001.83.61c.45.18 1.11.24 1.33 0 .22-.24-.27-1.31-.8-1.31a1.22 1.22 0 00-1-1 1.358 1.358 0 00-1.53.82z" />
<path d="M7.477 147.235a1.74 1.74 0 001.84.77.51.51 0 00.24.88c.57.1 1.17.37 1.78 0 .07-.19.14-.41.21-.61a.564.564 0 00.68-.17.782.782 0 00.25-.51.8.8 0 00.29-.68.76.76 0 00-.72-.63c-.69-.07-.92.11-1.21.89l-1 .48a1.939 1.939 0 00.14-2.15 1.362 1.362 0 00-1.09-.55.78.78 0 00-.44-.33s-.29.27-.27.36l.05.1-.18.08a1.367 1.367 0 00-.812 1.508c.037.203.12.395.242.562z" />
<path d="M11.377 131.995c-.54-.23-.86.13-1.06.57a.918.918 0 001.3.65c.18-.53.33-1.02-.24-1.22z" />
<path d="M10.947 144.585a.31.31 0 00-.08.5.305.305 0 00.235.158.31.31 0 00.265-.098.35.35 0 10-.42-.56z" />
<path d="M12.377 131.095a1.45 1.45 0 002.33-.51 10.49 10.49 0 00-2.33.5v.01z" />
<path d="M16.227 131.475a1.311 1.311 0 00-.11 1.5c.37.36 1.13.2 1.63-.34a.624.624 0 00.222-.512.632.632 0 00-.272-.488 1.121 1.121 0 00-1.47-.16z" />
<path d="M18.347 138.925a.838.838 0 001.17.1 1.75 1.75 0 00.09-1.8c-.46-.43-1-.35-1.5.22s-.37.98.24 1.48z" />
<path d="M7.337 137.995c.035.051.08.095.13.13a1.579 1.579 0 002-.24 1.688 1.688 0 000-2.12 1.654 1.654 0 00-1.73-.33 1.633 1.633 0 00-.61-.25 10.013 10.013 0 00-1.3 2.81 1.217 1.217 0 001.51 0z" />
<path d="M30.127 173.445a.727.727 0 00.14-1.07 1.003 1.003 0 00-.79-.37c-.59.15-.69.67-.62 1.25.39.27.82.53 1.27.19z" />
<path d="M8.937 133.195a.617.617 0 00.17-.28c-.17.14-.32.29-.48.44a.775.775 0 00.31-.16z" />
<path d="M20.097 158.895a.8.8 0 00-.22.7.516.516 0 00.572.37.512.512 0 00.228-.09c.19-.13.51-.77.35-1-.16-.23-.77-.1-.93.02z" />
<g id="Group 69">
<path d="M21.927 135.065a.639.639 0 00-.268.445.627.627 0 00.398.66.735.735 0 00.968-.401.65.65 0 00-.228-.764.588.588 0 00-.87.06z" />
<path d="M21.737 130.635a.3.3 0 00.19-.1h-.59a.517.517 0 00.4.1z" />
<path d="M11.737 134.815a1.39 1.39 0 00.31 1.39 1.184 1.184 0 001.25-.38.635.635 0 00.068-.519.626.626 0 00-.348-.391c-.56-.34-1.06-.38-1.28-.1z" />
<path d="M12.027 141.575c.05-.15 0-.48-.26-.52a.427.427 0 00-.49.23.642.642 0 00.25.61c.21.1.43-.14.5-.32z" />
<path d="M9.987 140.045a1.856 1.856 0 001.83 0 1.049 1.049 0 00-.15-1.54c-.51-.51-.91-.49-1.48.1s-.58 1.08-.2 1.44z" />
<path d="M15.007 134.995a.828.828 0 00.12.68c.264.336.559.648.88.93.27.26.52.19.78-.11 0-.11.05-.31 0-.39a1.75 1.75 0 01-.38-1.08c0-.18-.42-.42-.66-.42a1.009 1.009 0 00-.74.39z" />
<path d="M18.737 134.325a.94.94 0 000 1.2 1.212 1.212 0 001.31.06 1.19 1.19 0 00-.11-1.41.86.86 0 00-1.2.15z" />
<path d="M13.467 132.075a1.179 1.179 0 00-.11 1.3.61.61 0 00.45.262.609.609 0 00.49-.172c.54-.42.77-.87.58-1.15a1.407 1.407 0 00-1.41-.24z" />
</g>
<g id="Group 68">
<path d="M19.487 163.255a.74.74 0 00-.23-.36c-.05-.18-.22-.44-.37-.43-.15.01-.15.08-.21.18h-.08a1.007 1.007 0 00-.494.649 1 1 0 00.154.801c.32.24 1.32-.36 1.23-.84z" />
<path d="M18.887 170.585c-.07-.25-.42-.61-.63-.6-.21.01-.49.36-.66.49a.364.364 0 000 .1 1.083 1.083 0 00-1 .17c-.58.37-.79.94-.5 1.37a1.683 1.683 0 002 .48 1.208 1.208 0 00.17-1.4c.36-.02.73-.2.62-.61z" />
<path d="M17.997 168.345a.764.764 0 00-.363.769c.015.1.05.195.103.281a.586.586 0 00.414.324.574.574 0 00.506-.144c.161-.225.277-.48.34-.75-.26-.54-.68-.67-1-.48z" />
<path d="M18.797 156.735a6.55 6.55 0 00-.54-1c.07-.16.15-.32.21-.49a1.657 1.657 0 00-.44-1.79 1.914 1.914 0 00-1.88-.14 1.003 1.003 0 00-.48.54 1.52 1.52 0 00.88 2l.5.22c-.15.32-.32.61-.43.91-.05.132-.08.27-.09.41a.332.332 0 00-.12.33l-.61-.1a4.156 4.156 0 00-.22-.48 1.653 1.653 0 00-1.64-.85 1.771 1.771 0 00-.74.39.479.479 0 00-.16-.26.68.68 0 00-.75.12c-.2.23.2.68.36.74h.09l-.09.2c-.44.47-.59 1.38-.27 1.72.187.187.428.31.69.35a1.19 1.19 0 00-.85.13 1.411 1.411 0 00-.44 1.84 1.537 1.537 0 001.71.78c-.083.182-.124.38-.12.58 0 .66.63 1.44 1.12 1.43a1.497 1.497 0 001.31-1.14 1.188 1.188 0 001.13-.48.749.749 0 00-.24-1c0-.15.07-.32 0-.4-.07-.08-.58 0-.67.12a.661.661 0 00-.07.45 3.262 3.262 0 00-.29.59 1.66 1.66 0 00-1.29-.71 1.109 1.109 0 00.12-1.27 2.103 2.103 0 00-1.34-.89 1.381 1.381 0 001.13-.26.83.83 0 00.18-.28c.095-.007.189-.024.28-.05l.5-.22c.14.32.25.63.4.92a.81.81 0 001.18.44 1.255 1.255 0 00.85-1.17.901.901 0 00-.19-.63 1.238 1.238 0 001.05-.26 1.117 1.117 0 00.27-1.34z" />
<path d="M25.737 171.115c-.13.16-.56.21-.48 0 .08-.21-.05-.47-.6-.68a.863.863 0 00-1.006.125.869.869 0 00-.204.295 1.282 1.282 0 00.62 1.67c.63.31.91 0 1.3.24s.65.09 1-.25c.09-.09.19-.19.28-.3a1.003 1.003 0 001.14.21 1.36 1.36 0 00.63-1.68 1.44 1.44 0 00-1.74-.5.889.889 0 00-.39.67.56.56 0 00-.55.2z" />
<path d="M23.147 167.185c.21.38.67.19.82.1.15-.09.17-.37.24-.59.24 0 .43-.28.43-.38 0-.1 0-.43-.24-.5a.29.29 0 00-.128-.008.297.297 0 00-.252.258.84.84 0 00.06.53c-.19-.08-.38-.18-.51-.13-.13.05-.62.34-.42.72z" />
<path d="M23.437 174.755a1.352 1.352 0 00-1.24 0c-.38.28-.9.42-1 .81a.79.79 0 00.17.71h3a1.267 1.267 0 00-.42-.24c.042-.129.059-.265.05-.4a1.12 1.12 0 00-.56-.88z" />
<path d="M21.327 168.995a1.38 1.38 0 00.06-.88l-.47-.3a.67.67 0 00-.12-.54.577.577 0 00-.2-.13c-.14-.51-.44-.88-.78-.9a.766.766 0 00-.76.49c-.56-.07-.83.33-1 .93.37.37.65.83 1.23.57a1.74 1.74 0 00.22-.14l.11.08c.07.522.174 1.04.31 1.55a1.608 1.608 0 00-.18.83c0 1.2 1.24 1 1 1.72s-.84.17-1.15 1.25c-.005.07-.005.14 0 .21-.2-.49-.63-1-.93-1.05-.54 0-1.14.62-1.22 1.34-.01.09-.01.18 0 .27a.811.811 0 00-.26.1.699.699 0 00-.25.39.687.687 0 00-.48-.43 1.778 1.778 0 00-1.55.95.858.858 0 00.59 1h1.71a.489.489 0 00.08-.12.923.923 0 00.15.12h1.1a1.362 1.362 0 00.45-1.26c.251-.116.456-.313.58-.56a1.004 1.004 0 00.793.803c.131.025.266.024.397-.003.76-.22.87-.91 1.06-.91.19 0 1 .23 1.17-.38.35-1.29-.88-1.3-.89-1.72-.01-.42.45-1.11.48-1.72.04-.83-.38-1.56-1.25-1.56zm-4.44 6.79v-.12a.901.901 0 00.05.15s-.02-.05-.05-.05v.02z" />
<path d="M22.387 163.115a1.171 1.171 0 00.39 1.43c.72-.37 1-.71.81-1.1-.25-.55-.7-.52-1.2-.33z" />
</g>
</g>
<g fill="#343741" className={dataViewIllustrationVerticalStripes}>
<path d="M121.737 136.495h-6v31h6v-31z" />
<path d="M109.737 136.495h-6v31h6v-31z" />
</g>
<path
id="circle A"
fill="#F990C6"
d="M31.027 162.955a8.91 8.91 0 008.91-8.91 8.91 8.91 0 10-8.91 8.91z"
/>
</g>
<g fill="#ECF0F6" className={dataViewIllustrationDots}>
<path d="M63.807 81.175a9.64 9.64 0 00-.79 1.91c-.75.13-1.61.36-1.85 1.23a1.829 1.829 0 00.57 1.76c.77-1.79 1.6-3.54 2.49-5.27a1.179 1.179 0 00-.42.37z" />
<path d="M59.677 89.995a4.26 4.26 0 00.06 1.22c.26-.74.51-1.49.8-2.22a1.12 1.12 0 00-.86 1z" />
<path d="M61.217 80.415c.06.2.41.39.66.43a.689.689 0 00.57-.33c.2-.49-.07-.82-.57-1-.38.16-.83.35-.66.9z" />
<path d="M56.137 79.495c.18.2.68.68 1 .53.32-.15.13-.88 0-1.1 0 0-.07-.06-.14-.09h-.64a.4.4 0 00-.22.66z" />
<path d="M55.937 83.615c.103-.323.14-.663.11-1-.11-.45-.76-.58-1.21-.32a.73.73 0 00-.23 1 1 1 0 001.33.32z" />
<path d="M57.737 80.895a1.53 1.53 0 00-.59 1.73 1.21 1.21 0 001.59.37c.73-.36 1-.8.7-1.36a1.43 1.43 0 00-1.7-.74z" />
<path d="M47.437 82.525c.77.09 1.6-.57 1.51-1.2a1.2 1.2 0 00-1-1.06 1 1 0 00-1.18.57c-.18.38-.76 1-.41 1.54.35.54.79.12 1.08.15z" />
<path d="M55.127 85.555a1.19 1.19 0 00-1.5.14.81.81 0 00.11 1.11.8.8 0 001 .19 1.26 1.26 0 00.39-1.44z" />
<path d="M51.937 87.795c-.31-.68-1.17-1.11-1.68-.84a1.27 1.27 0 00-.33 1.57 1.15 1.15 0 001.52.4c.54-.26.71-.65.49-1.13z" />
<path d="M52.447 78.275c.24-.2.75-.77.6-1-.15-.23-.89-.21-1.19-.15-.3.06-.43.61-.22.93.21.32.51.46.81.22z" />
<path d="M51.737 90.295a1.69 1.69 0 00-.62 1.79h2.78a1.2 1.2 0 000-1.11 1.64 1.64 0 00-2.16-.68z" />
<path d="M57.497 86.995a.83.83 0 00-.38 1.1.86.86 0 101.53-.79.94.94 0 00-1.15-.31z" />
<path d="M56.827 90.385c-.28-.58-.79-.84-1.21-.61a2.179 2.179 0 00-.76 1.69 1.178 1.178 0 001.69.44c.52-.26.62-.8.28-1.52z" />
<path d="M45.277 90.195c-.09 0-.22.35-.17.4a.62.62 0 00.48.25c.12 0 .45-.22.35-.54-.1-.32-.49-.15-.66-.11z" />
<path d="M51.127 75.115c.15-.07.46-.23.4-.5s-.47-.24-.53-.19a.67.67 0 00-.25.47c-.01.1.3.26.38.22z" />
<path d="M51.547 80.485a.79.79 0 00-.37 1 .79.79 0 101.49-.49 1 1 0 00-1.12-.51z" />
<path d="M46.897 86.265a.69.69 0 00-.44 1 .85.85 0 001 .42.73.73 0 00.44-1 .83.83 0 00-1-.42z" />
<path d="M60.167 85.415a.87.87 0 00-.88-.68 1.001 1.001 0 00-.9 1.11 1.07 1.07 0 001.43.49.69.69 0 00.35-.92z" />
<path d="M57.147 91.625a.81.81 0 00-.41.37.23.23 0 000 .14h.75v-.06c.12-.32-.26-.47-.34-.45z" />
<path d="M48.327 90.695a.45.45 0 00-.22.57.481.481 0 00.85-.45.52.52 0 00-.63-.12z" />
</g>
<g fill="#ECF0F6 " className={dataViewIllustrationDots}>
<path d="M76.787 187.575a.271.271 0 00-.22.25c0 .08.16.2.23.2s.22-.07.23-.18a.259.259 0 00-.066-.183.257.257 0 00-.174-.087z" />
<path d="M78.497 184.765a.843.843 0 00-.8.87.792.792 0 00.536.751.775.775 0 00.324.039c.54 0 .91-.22.89-.58a1.36 1.36 0 00-.95-1.08z" />
<path d="M75.847 183.995a.593.593 0 00-.44.48.531.531 0 00.34.41c.12 0 .39-.18.41-.3.02-.12-.06-.59-.31-.59z" />
<path d="M99.737 189.875a1.49 1.49 0 00-.68 1.16c0 .71.49 1.15 1.31 1.15a1.131 1.131 0 001.26-1.2c.01-.08.01-.16 0-.24-.65-.29-1.27-.56-1.89-.87z" />
<path d="M79.847 188.525c-.11 0-.56.16-.57.3-.01.14.36.38.53.42.17.04.36-.2.34-.27-.02-.07-.16-.41-.3-.45z" />
<path d="M79.627 176.795a.772.772 0 00-.74.88c0 .39.07.82.48.84.72 0 1.14-.3 1.17-.74a.897.897 0 00-.543-.908.907.907 0 00-.367-.072z" />
<path d="M72.507 182.775c-.23-.07-1.13.32-1.17.74-.04.42.92.77 1.27.79a.943.943 0 00.83-.72c.03-.41-.57-.72-.93-.81z" />
<path d="M71.937 178.275a.751.751 0 00-.69-.82.828.828 0 00-.672.137.844.844 0 00-.135 1.245.84.84 0 00.627.278 1 1 0 00.87-.84z" />
<path d="M75.987 180.455a.838.838 0 00.912-.441.827.827 0 00.088-.349.898.898 0 00-.78-.89 1.003 1.003 0 00-.93.89.79.79 0 00.71.79z" />
<path d="M68.417 177.325c-.12 0-.55.1-.57.31-.02.21.36.37.52.42.16.05.35-.11.35-.27a.586.586 0 00-.3-.46z" />
<path d="M76.197 176.535a.835.835 0 000-.22h-2.28a1.071 1.071 0 001 1.07c.66.02 1.27-.39 1.28-.85z" />
<path d="M74.297 180.995c-.37-.05-.92.63-.82.89.1.26.44.37.74.43a.484.484 0 00.467-.088.487.487 0 00.173-.442c-.01-.41-.12-.79-.56-.79z" />
<path d="M82.487 183.495c.43 0 1-.74 1-1.4a1.003 1.003 0 00-.93-.89 1.163 1.163 0 00-1.18.93 1.492 1.492 0 001.11 1.36z" />
<path d="M82.167 177.905c-.43-.38-.87-.75-1.29-1.14a.698.698 0 00-.21.47c0 .66.54.85 1 1.07.178-.118.345-.252.5-.4z" />
<path d="M91.157 185.325a1.396 1.396 0 00-1.3.58h-.06c-.23 0-.89.26-.9.52-.01.26.51.59.71.6a1.739 1.739 0 001.46 1.36 1.791 1.791 0 001.67-1.51c.11-.8-.56-1.45-1.58-1.55z" />
<path d="M93.737 192.825a.645.645 0 00-.24.59.707.707 0 00.65.38c.3-.05.28-.65.2-.8a.613.613 0 00-.61-.17z" />
<path d="M91.667 189.585a.579.579 0 00-.49.25c0 .11 0 .55.24.6.24.05.42-.32.48-.48.06-.16-.07-.35-.23-.37z" />
<path d="M94.657 191.995a.271.271 0 00-.23.25c0 .08.17.2.24.2.07 0 .22-.07.22-.18a.248.248 0 00-.136-.243.26.26 0 00-.094-.027z" />
<path d="M91.047 190.265a.462.462 0 00-.348.03.443.443 0 00-.222.27c-.08.33.36.38.52.42.16.04.36-.2.35-.27-.01-.07-.16-.41-.3-.45z" />
<path d="M99.377 194.615a1.783 1.783 0 00-1.8 1.27 1.609 1.609 0 001.14 1.86 2.326 2.326 0 002-1.43 1.58 1.58 0 00-1.34-1.7z" />
<path d="M98.357 189.915c.06-.54-.18-.93-.62-1a.985.985 0 00-.39 0 1.505 1.505 0 00-.91-.49 1.227 1.227 0 00-.943.148 1.225 1.225 0 00-.547.782 1.91 1.91 0 001.25 2.05 1.419 1.419 0 001.18-.5 1.212 1.212 0 00.98-.99z" />
<path d="M84.267 183.615c-.42 0-.75.25-.66.63.09.38.14.58.39.67.25.09.87-.42.86-.83-.01-.41-.3-.45-.59-.47z" />
<path d="M81.927 184.845c-.21 0-.39.32-.39.43 0 .11 0 .54.25.53a.552.552 0 00.45-.36c.03-.14-.1-.62-.31-.6z" />
<path d="M86.807 182.935a1.301 1.301 0 001-.41c-.56-.41-1.11-.82-1.66-1.25-.24.09-.5.19-.75.31a.747.747 0 00-.386.426.742.742 0 00.036.574.773.773 0 001 .43c.247-.069.504-.096.76-.08z" />
<path d="M88.317 192.685c-.23 0-.63.18-.67.36-.04.18.26.54.45.6.19.06.78-.16.8-.38.02-.22-.44-.59-.58-.58z" />
<path d="M83.337 188.605a.812.812 0 00-.88.78.64.64 0 00.64.81.808.808 0 00.82-.418.803.803 0 00.09-.312.848.848 0 00-.67-.86z" />
<path d="M86.867 185.055a.72.72 0 00-.78.6c-.08.34.55 1 .86.87a.998.998 0 00.54-.65.65.65 0 00-.62-.82z" />
<path d="M86.537 189.255c-.3 0-.59.41-.61.88a.459.459 0 00.5.56c.38 0 .73-.12.79-.58a.889.889 0 00-.68-.86z" />
<path d="M80.077 181.995a.78.78 0 00-.15-.75 1.292 1.292 0 00-.48-.199 1.3 1.3 0 00-.52.009c-.61.23-.35.77-.28 1.29.64.18 1.17.24 1.43-.35z" />
<path d="M88.847 183.995a1.48 1.48 0 00.08-.71l-.63-.46h-.06a.89.89 0 00-.2.69c0 .24-.06.48.11.69.17.21.59.16.7-.21z" />
<path d="M98.647 193.605a.799.799 0 001-.44.999.999 0 00-.07-.79c-.25-.42-.63-.4-1.46 0 .02.47-.13 1.03.53 1.23z" />
<path d="M94.997 187.265l-1-.58a1.17 1.17 0 00.13.69c.11.14.5.05.87-.11z" />
<path d="M186.557 181.565a.404.404 0 00.308-.286.398.398 0 00-.118-.404 1.988 1.988 0 00-.29-.26c-.24.2-.5.39-.75.59.023.095.06.186.11.27.08.13.51.17.74.09z" />
<path d="M109.117 193.995a.93.93 0 00.4 0l-.93-.33a.77.77 0 00.53.33z" />
<path d="M195.737 173.815a.681.681 0 00.446-.401.687.687 0 00-.036-.599.859.859 0 00-1-.4l-.41.58a.748.748 0 000 .49.838.838 0 001 .33z" />
<path d="M191.307 179.745a.808.808 0 00-.08-1.435.806.806 0 00-.605-.01.78.78 0 00-.435.421.797.797 0 00-.01.604 1 1 0 001.13.42z" />
<path d="M127.137 198.445a.52.52 0 00-.1-.51h-.22a.905.905 0 00-.34.37c0 .09.11.28.17.43.17-.1.39-.15.49-.29z" />
<path d="M168.827 193.515c.1.15.32.44.49.33a.536.536 0 00.19-.54c0-.14-.4-.48-.57-.36a.544.544 0 00-.11.57z" />
<path d="M84.627 180.465c.166.011.333.011.5 0-.43-.35-.87-.68-1.29-1a1.062 1.062 0 00-.06.29.767.767 0 00.544.681.77.77 0 00.306.029z" />
<path d="M165.937 197.935a.262.262 0 00-.06.33c0 .07.24.09.3 0s.15-.17.1-.27a.249.249 0 00-.34-.06z" />
<path d="M165.957 194.645a.842.842 0 00-.028 1.39.79.79 0 00.628.131.79.79 0 00.3-.131c.48-.26.67-.65.47-1a1.364 1.364 0 00-1.37-.39z" />
<path d="M164.867 193.625c.51 0 .73-.43.72-.86-.48.19-1 .36-1.47.54.212.179.474.291.75.32z" />
<path d="M169.047 197.175c-.12 0-.39.42-.33.55.06.13.5.14.67.09.17-.05.2-.36.15-.41-.05-.05-.34-.27-.49-.23z" />
<path d="M170.537 190.595l-.32.15a.517.517 0 00-.15.82c.19.2.42.43.68.38.26-.05.53-.81.31-1.15a.42.42 0 00-.52-.2z" />
<path d="M172.077 195.445a.822.822 0 00-.36 1.12.66.66 0 001 .37.811.811 0 00.4-1.1.838.838 0 00-1.04-.39z" />
<path d="M163.307 195.385a.593.593 0 00-.13.64.536.536 0 00.51.18.541.541 0 00.19-.48c-.06-.14-.36-.48-.57-.34z" />
<path d="M168.857 191.375l-.45.2c.11-.006.218-.029.32-.07a.502.502 0 00.13-.13z" />
<path d="M154.507 201.475a.654.654 0 00-.29.56.63.63 0 00.52.36.694.694 0 00.51-.56c.01-.3-.58-.4-.74-.36z" />
<path d="M152.537 197.625a1.514 1.514 0 00-1.726.339 1.505 1.505 0 00-.324.531 1.222 1.222 0 00.6 1.65 1.911 1.911 0 002.26-.8 1.403 1.403 0 00-.81-1.72z" />
<path d="M151.027 203.245c-.08-.05-.36 0-.41.15a.59.59 0 00.15.53c.09.07.54.08.63-.12.09-.2-.22-.46-.37-.56z" />
<path d="M155.737 196.325a1.01 1.01 0 00-.78-.1c-.46.16-.52.54-.31 1.43.47.08 1 .34 1.32-.27a.777.777 0 00-.23-1.06z" />
<path d="M159.797 195.995a.746.746 0 00-.29.25 1.16 1.16 0 00-.44-.37 1.582 1.582 0 00-1.94 1 1.77 1.77 0 00.87 2 1.603 1.603 0 002.05-.74 1.44 1.44 0 000-.72c.212-.02.418-.078.61-.17a.94.94 0 00.34-1c-.17-.38-.85-.33-1.2-.25z" />
<path d="M148.627 198.825c-.33-.16-.86.45-.84 1.09.02.64.88.72 1.18.6.3-.12-.01-1.52-.34-1.69z" />
<path d="M184.627 183.095a2.004 2.004 0 00-.91 2.25 1.6 1.6 0 002 .65 1.548 1.548 0 00.77-1.93 1.59 1.59 0 00-1.86-.97z" />
<path d="M187.177 187.655a1.38 1.38 0 00-.36 1.77 1.123 1.123 0 00.787.562 1.134 1.134 0 00.933-.252 1.114 1.114 0 00.625-.726 1.12 1.12 0 00-.165-.944 1.323 1.323 0 00-1.82-.41z" />
<path d="M183.557 192.565a.25.25 0 00-.07.33c0 .07.24.08.3 0a.228.228 0 00.121-.136.214.214 0 00.008-.094.216.216 0 00-.096-.155.226.226 0 00-.087-.034.209.209 0 00-.093.006.213.213 0 00-.083.043v.04z" />
<path d="M173.277 190.585a.717.717 0 00-.36.91c.11.33 1 .57 1.19.3a.9.9 0 00.13-.82.658.658 0 00-.96-.39z" />
<path d="M196.397 177.685c-.37-.49-.75-.09-1-.12-.77-.06-1.58.62-1.47 1.25a1.208 1.208 0 001 1 .942.942 0 001.15-.61c.13-.35.66-1.03.32-1.52z" />
<path d="M190.477 181.995c-.23.2-.72.79-.56 1 .16.21.89.18 1.19.11.3-.07.41-.62.19-.94-.22-.32-.56-.44-.82-.17z" />
<path d="M191.897 185.085c-.14.08-.45.25-.38.52s.47.23.54.18a.75.75 0 00.23-.49c0-.07-.31-.25-.39-.21z" />
<path d="M184.587 188.295a1.373 1.373 0 00-.67 1.58.943.943 0 001.2.47 1.177 1.177 0 00.51-1.51.78.78 0 00-.15-.29.808.808 0 00-.89-.25z" />
<path d="M179.257 189.115a1.507 1.507 0 00-.97-.727 1.501 1.501 0 00-1.19.227 1.375 1.375 0 00-.75.804 1.36 1.36 0 00.09 1.096 1.72 1.72 0 002.16.72 1.806 1.806 0 00.66-2.12z" />
<path d="M175.157 194.345c-.27.14-.29.67-.07 1.08a.466.466 0 00.521.304.469.469 0 00.199-.084c.32-.21.56-.48.37-.9a.885.885 0 00-.441-.375.885.885 0 00-.579-.025z" />
<path d="M173.737 188.995a.564.564 0 00.28.25c.29.11.51-.21.47-.57a.18.18 0 000-.08l-.75.4z" />
<path d="M178.417 186.295a1.814 1.814 0 001.46 1c.7 0 .9-.36.93-1.78.55-.05 1.12 0 1.14-.75a1.003 1.003 0 00-.22-.68c-1.08.79-2.18 1.51-3.31 2.21z" />
<path d="M178.447 196.385c-.19.12-.44.47-.38.65.06.18.49.33.69.28.2-.05.58-.54.49-.74-.09-.2-.68-.28-.8-.19z" />
<path d="M179.547 192.895c-.12 0-.45.24-.34.56.11.32.5.13.67.09.17-.04.2-.36.15-.41a.646.646 0 00-.48-.24z" />
<path d="M147.737 197.525l-.34.06a.548.548 0 00-.22.26c0 .16 0 .45.16.55.16.1.44-.09.53-.23.09-.14.09-.52-.13-.64z" />
<path d="M117.737 204.935a.25.25 0 00-.29.2c0 .16.14.24.22.26.08.02.22-.14.22-.21 0-.07-.08-.19-.15-.25z" />
<path d="M115.167 201.495a1.093 1.093 0 00-.874.209 1.111 1.111 0 00-.426.791c-.1.7.25 1.29.79 1.34a1.612 1.612 0 001.61-1.23c.1-.45-.47-1.05-1.1-1.11z" />
<path d="M113.107 198.995c-.3 0-1 .12-1 .53 0 .41.48.63.72.73a.548.548 0 00.79-.33c.12-.41-.16-.93-.51-.93z" />
<path d="M111.737 195.415a1.385 1.385 0 001.38.58 1.001 1.001 0 00.67-.64c-.74-.21-1.47-.44-2.2-.68-.033.256.02.516.15.74z" />
<path d="M120.737 197.715a.847.847 0 00-.54.52c0 .2.06.78.32.86.26.08.64-.4.75-.61a.589.589 0 00-.53-.77z" />
<path d="M120.657 202.885a.694.694 0 00-.6.27.874.874 0 000 .77.766.766 0 00.269.176.783.783 0 00.881-.226.74.74 0 00-.55-.99z" />
<path d="M118.127 197.885a1.075 1.075 0 00-.35-.27.998.998 0 00-1.186-.056.997.997 0 00-.404 1.116c.053.581.18 1.152.38 1.7a1.636 1.636 0 001.75.81c.37-.09 1.08-.56.72-1.25-1.08-.74-.3-1.28-.91-2.05z" />
<path d="M112.597 204.535a.545.545 0 00-.48.25c0 .11 0 .55.24.6.24.05.41-.32.47-.48.06-.16-.09-.35-.23-.37z" />
<path d="M105.167 195.565a.816.816 0 00-.414-.842.82.82 0 00-.316-.098c-.39 0-.48.24-.54.54-.06.3.18.77.56.72.252-.068.492-.176.71-.32z" />
<path d="M121.737 197.345l.25-.11-.37-.08c0 .06.05.12.12.19z" />
<path d="M105.937 199.555a2 2 0 00-1.79 2c.08.68 1 .67 1.36.35.36-.32.29-.16.77 0 .48.16 1.24.12 1.27-.78a1.7 1.7 0 00-1.61-1.57z" />
<path d="M104.737 193.655a1.74 1.74 0 00-.8-1.52 1.133 1.133 0 00-1.19.557 1.122 1.122 0 00-.14.443c-.11.51.32 1 1 1.15a.825.825 0 00.719-.039.814.814 0 00.411-.591z" />
<path d="M107.647 195.685a.824.824 0 00-.605.2.83.83 0 00-.285.57.77.77 0 00.58.9.74.74 0 001-.6.897.897 0 00-.69-1.07z" />
<path d="M109.177 198.415c-.44.13-.92.26-.88.91v1.44a.683.683 0 00-.032.611.689.689 0 00.472.389.921.921 0 001.12-.6c.098-.288.255-.554.46-.78a1.216 1.216 0 00.48-1.34c-.13-.39-1.14-.78-1.62-.63z" />
<path d="M133.467 204.325c-.25.46.3.88.42 1.25h.27l.74-1.09c0-.11-.1-.21-.14-.32-.38.06-1.02-.3-1.29.16z" />
<path d="M140.117 200.295c-.39-.17-.64.26-.7.43-.06.17.12.43.19.65.22-.08.53-.1.62-.26.09-.16.29-.65-.11-.82z" />
<path d="M124.407 201.505a.832.832 0 00-.648.131.83.83 0 00-.352.559.896.896 0 00.77 1 .836.836 0 00.9-.423.833.833 0 00.1-.337.883.883 0 00-.77-.93z" />
<path d="M138.457 204.135a.504.504 0 00.26.44.56.56 0 00.5-.23c.05-.12-.13-.34-.25-.61-.23.17-.46.26-.51.4z" />
<path d="M146.617 204.475c-.07.22.07.92.33 1 .26.08.68-.38.73-.57a.813.813 0 00-.27-.69.507.507 0 00-.667.053.508.508 0 00-.123.207z" />
<path d="M142.057 202.125c-.34.74-.34 1.18.05 1.37.39.19.88 0 1.15-.49a1.168 1.168 0 00-1.2-.88z" />
<path d="M145.887 199.665c-.42 0-.82 1.1-.45 1.42a.835.835 0 001.08-.07c.3-.4-.22-1.35-.63-1.35z" />
<path d="M142.587 198.605a1.31 1.31 0 00.12-.48l-1.29.11a.719.719 0 00.5.54c.233-.006.462-.064.67-.17z" />
<path d="M126.927 200.665c.05-.14 0-.48-.17-.53a.54.54 0 00-.51.22c-.07.12-.06.56.1.66.16.1.54-.21.58-.35z" />
<path d="M134.737 202.625c-.06.11 0 .24.12.29.12.05.23 0 .23-.06a1.362 1.362 0 00-.07-.34c-.06.03-.2-.01-.28.11z" />
<path d="M127.307 201.645a.55.55 0 00-.48.25c-.05.11 0 .55.24.6.24.05.41-.32.47-.48a.331.331 0 00-.23-.37z" />
<path d="M124.677 197.645a.84.84 0 00-.36.48.641.641 0 00.038.583.654.654 0 00.492.317.792.792 0 00.886-.296.789.789 0 00-.016-.934c-.35-.04-.7-.1-1.04-.15z" />
<path d="M128.157 198.495a.743.743 0 00-.571.113.75.75 0 00-.319.487.832.832 0 00.626 1.01.823.823 0 00.634-.116.84.84 0 00.36-.534.998.998 0 00-.73-.96z" />
<path d="M102.517 197.815c-.39 0-.48.24-.54.53-.06.29.18.77.56.72.251-.067.49-.172.71-.31a.809.809 0 00-.73-.94z" />
</g>
<g id="circle cross">
<g fill="#ECF0F6" className={dataViewIllustrationDots}>
<path d="M147.567 14.355a.991.991 0 00.52.608 1.001 1.001 0 00.8.032c.64-.15 1.28-.91 1.17-1.39a1.463 1.463 0 00-1.61-1 1.479 1.479 0 00-.88 1.75z" />
<path d="M147.987 9.895a.62.62 0 00.31-.47c0-.15-.35-.29-.5-.26-.15.03-.5.32-.45.49.05.17.5.34.64.24z" />
<path d="M208.817 56.285c-.17.5-.35 1-.54 1.46.17 0 .35-.18.53-.56a.897.897 0 00.01-.9z" />
<path d="M140.677 12.885a.775.775 0 00.008-.522.775.775 0 00-.328-.408.71.71 0 00-1 .8.745.745 0 00.62.475.74.74 0 00.7-.345z" />
<path d="M153.737 12.095c.6-.38 1.21-.74 1.83-1.08a.868.868 0 00-.557-1.082.869.869 0 00-.443-.028c-.89.1-1.14.42-1 1.31.03.29.07.58.17.88z" />
<path d="M141.537 17.735a.761.761 0 00-.31-.93c-.38-.19-.61 0-1 .79a.725.725 0 00.611.483.73.73 0 00.699-.343z" />
<path d="M143.867 16.805a.94.94 0 00.69-.23 1.513 1.513 0 00.2-2.13l-.32-.45c.284-.175.557-.365.82-.57a.8.8 0 00.2-1.24 1.266 1.266 0 00-1.31-.61 1.1 1.1 0 00-.9 1 5.775 5.775 0 000 1.1c-.14.1-.29.2-.43.32a1.658 1.658 0 00-.5 1.77 1.886 1.886 0 001.55 1.04z" />
<path d="M152.597 9.445c.28-.44.68-.81.31-1.32a.726.726 0 00-.452-.324.721.721 0 00-.548.094 1 1 0 00-.46.65c0 .65.48.84 1.15.9z" />
<path d="M183.147 87.265a.772.772 0 001 .2c.35-.24.3-.52-.21-1.22a.742.742 0 00-.842.636.741.741 0 00.052.384z" />
<path d="M191.827 76.645l-.29.17c.098-.043.192-.097.28-.16l.01-.01z" />
<path d="M146.137 73.835a1.5 1.5 0 00-1.1-1.66 1 1 0 00-1.17.73.714.714 0 00-.25-.06 1.002 1.002 0 00-.9.91.77.77 0 00.75.77 1 1 0 00.59-.15.79.79 0 00.38.33 1.468 1.468 0 001.7-.87z" />
<path d="M206.187 68.885c-.16 0-.57-.09-.64.14-.07.23.25.45.35.45.1 0 .43.07.51-.19a.29.29 0 00-.22-.4z" />
<path d="M139.927 22.575c.71 0 .82-.39 1.28-.34.46.05.62-.2.79-.66.17-.46.38-1.17-.27-1.37-.65-.2-.86.19-.91.39-.05.2-.4.43-.43.2-.03-.23-.25-.4-.83-.35a.85.85 0 00-.91.9 1.237 1.237 0 00.384.882 1.248 1.248 0 00.896.348z" />
<path d="M155.457 9.315c.6-.17.65-.51.56-.83-.09-.32-.35-.64-.71-.45s-.52.53-.46.73c.06.2.45.42.61.55z" />
<path d="M128.797 38.925c-.1 0-.15.26-.23.41.18.08.35.22.52.21.17-.01.39-.25.34-.46-.05-.21-.46-.19-.63-.16z" />
<path d="M127.737 34.995a.752.752 0 00.236-.962.752.752 0 00-.236-.268c-.21-.12-.69.21-1 .37-.09.05-.11.24-.2.46.4.15.86.69 1.2.4z" />
<path d="M127.037 39.995c-.14 0-.47.31-.45.5.02.19.5.29.63.24.13-.05.36-.32.32-.47a.561.561 0 00-.5-.27z" />
<path d="M128.447 48.155c-.51.1-.5.79-.71 1.12l.15.22 1.32-.11.16-.31c-.27-.27-.41-1.01-.92-.92z" />
<path d="M129.417 28.515a.662.662 0 00-.465.605.67.67 0 00.045.265.595.595 0 00.509.459c.081.01.163.003.241-.019a.64.64 0 00.57-.76.721.721 0 00-.9-.55z" />
<path d="M131.427 57.755a.563.563 0 00-.31-.45c-.11 0-.54.1-.56.31-.02.21.36.37.53.41.17.04.34-.11.34-.27z" />
<path d="M130.847 48.255c-.08 0-.13-.18-.26-.15s-.15.19-.16.27c-.01.08.18.16.2.14.086-.075.16-.163.22-.26z" />
<path d="M135.067 25.125a1.399 1.399 0 00-.51 1.73c.21.62.76.8 1.53.51a1.15 1.15 0 00.63-1.25c-.17-.5-1.25-1.12-1.65-.99z" />
<path d="M134.007 20.605c.305.442.639.863 1 1.26a.538.538 0 00.92.012.543.543 0 00.08-.352c0-.58.13-1.22-.34-1.75l-.81-.1c-.38-.66-1-1.08-1.4-.9a.76.76 0 00-.48.83c-.02.69.21.88 1.03 1z" />
<path d="M137.737 30.075c.11.308.292.586.53.81.227-.633.477-1.253.75-1.86a1.22 1.22 0 00-.77 0 .999.999 0 00-.51 1.05z" />
<path d="M137.527 17.285c.24-.05.26-.44.22-.52-.04-.08-.16-.41-.43-.35a.298.298 0 00-.204.143.304.304 0 00-.026.247c.05.19.2.54.44.48z" />
<path d="M131.097 31.605a.84.84 0 001.09.45.94.94 0 00.62-1c-.12-.38-.81-.82-1.11-.7a1.171 1.171 0 00-.6 1.25z" />
<path d="M130.797 22.895c.38-.19.41-1.55 0-1.82s-1.23.54-1.12 1.06c.11.52.75.96 1.12.76z" />
<path d="M132.387 23.675c.15-.08.43-.32.39-.53-.04-.21-.49-.22-.56-.16a.815.815 0 00-.24.5c0 .05.33.23.41.19z" />
<path d="M134.257 29.655c.59-.2.78-.69.52-1.41-.18-.5-.56-.77-1-.67a1.809 1.809 0 00-1 1.53c.25.6.75.79 1.48.55z" />
<path d="M188.817 83.785c0-.18 0-.36-.06-.53a1.66 1.66 0 00-1.28-1.33 1.74 1.74 0 00-1 .21.42.42 0 00-.24-.12 12.62 12.62 0 00-1.25.25c-.8-.63-1.6-.75-2-.27a1.693 1.693 0 00.3 2c.54.45.92.32 1.86-.75.165.131.339.251.52.36a1.503 1.503 0 001.69 1.1h.54c0 .35 0 .68.09 1 .11.61.36.81 1 .79a1.252 1.252 0 001.18-.83 1.104 1.104 0 00-.4-1.27 5.09 5.09 0 00-.95-.61z" />
<path d="M167.037 81.215a.805.805 0 00.406.835.8.8 0 00.314.095c.53.11.93-.06 1-.42a1.001 1.001 0 00-.11-.54c-.61-.08-1.22-.17-1.83-.28.095.067.196.127.3.18 0 0-.07.08-.08.13z" />
<path d="M146.187 76.395c.62.15 1.14.2 1.4-.38a.795.795 0 00-.17-.74 1.222 1.222 0 00-1-.17c-.58.24-.3.77-.23 1.29z" />
<path d="M176.017 83.635a.916.916 0 00.64-.54.659.659 0 00-.48-.92.72.72 0 00-.87.46c-.13.36.43 1.06.71 1z" />
<path d="M180.597 81.685a.71.71 0 00.58-.33 1 1 0 000-.67l-1.19.22c0 .42.15.73.61.78z" />
<path d="M135.607 36.615c.34 0 .78-.65.75-1.07a.63.63 0 00-.77-.55c-.68.1-1.1.37-1.08.71a1.379 1.379 0 001.1.91z" />
<path d="M189.337 79.665a.91.91 0 00-.24-.75.694.694 0 00-.65-.1c-.47.26-.46.69-.22 1.17.43.08.91.21 1.11-.32z" />
<path d="M134.187 52.305a1.4 1.4 0 001.15-.91c.22-.07.51-.23.42-.56a.405.405 0 00-.23-.26v-.06a1.999 1.999 0 00-2.18-1.52c-.66.17-.54 1.06-.18 1.4.36.34.2.27.09.76-.11.49.03 1.24.93 1.15z" />
<path d="M134.897 33.345a.996.996 0 00.81 0 .804.804 0 00.23-.65 6.171 6.171 0 00-.3-1.25c-.1-.36-.36-.43-.73-.29 0 .1-.19.25-.17.35a1.7 1.7 0 01-.21 1.13c-.06.18.21.59.37.71z" />
<path d="M140.337 24.885a1.301 1.301 0 00-1.13.53c-.22.41-.63.76-.52 1.15a.81.81 0 00.165.333c.079.098.18.175.295.227.174.145.379.247.6.3.39-.79.81-1.57 1.26-2.33a1.221 1.221 0 00-.67-.21z" />
<path d="M208.077 58.255c-.15.37-.31.73-.48 1.09a.592.592 0 00.32-.15c.46-.42.31-.77.16-.94z" />
<path d="M206.857 61.865a.748.748 0 00-.26-.46c-.23.45-.48.9-.73 1.34a.761.761 0 00.57-.12 1 1 0 00.42-.76z" />
<path d="M131.437 62.595c-.16 0-.22.18-.23.26-.01.08.17.19.24.19.07 0 .22-.08.22-.19a.239.239 0 00-.23-.26z" />
<path d="M202.247 77.085a1.22 1.22 0 00-1.57.93 1.642 1.642 0 001.26 1.9 1.682 1.682 0 001.66-1.17c.14-.75-.39-1.44-1.35-1.66z" />
<path d="M173.297 81.495l.08.07a.548.548 0 00.44-.08h-.52v.01z" />
<path d="M201.617 72.635a1.388 1.388 0 001.61-.79c.1-.43-.7-1.34-1.26-1.43a1.143 1.143 0 00-1.112.962 1.143 1.143 0 00.762 1.258z" />
<path d="M194.937 77.195a.994.994 0 00-.113-.816 1 1 0 00-.687-.454.69.69 0 00-.85.49.893.893 0 00.26 1.09 1 1 0 001.39-.31z" />
<path d="M196.387 76.835a.829.829 0 001-.63.864.864 0 00-.369-.882.858.858 0 00-.957.003.865.865 0 00-.364.55 1 1 0 00.69.96z" />
<path d="M136.897 59.605a.806.806 0 00.37-.64.514.514 0 00-.241-.406.515.515 0 00-.469-.044c-.22.08-.68.63-.57.88.11.25.75.29.91.21z" />
<path d="M139.217 64.995a.838.838 0 00-.58-.47c-.21 0-.77.16-.81.43-.04.27.47.58.69.66.22.08.8-.31.7-.62z" />
<path d="M135.587 47.085c.095.241.23.464.4.66h.09c0-.41-.07-.83-.1-1.25a.499.499 0 00-.39.59z" />
<path d="M139.267 68.575a.65.65 0 00-.83.63.79.79 0 00.115.659.796.796 0 00.575.341.81.81 0 00.709-1.3.822.822 0 00-.569-.33z" />
<path d="M144.287 20.355c.18-.23.37-.45.56-.67a.506.506 0 00-.27.09.76.76 0 00-.29.58z" />
<path d="M155.577 77.455c0 .24-.06.48.11.69.17.21.55.08.7-.26.05-.138.077-.283.08-.43-.29-.14-.57-.28-.85-.44a1.28 1.28 0 00-.04.44z" />
<path d="M142.057 66.535a1 1 0 001-.56c-.39-.53-.75-1.07-1.11-1.62-.54 0-.93.3-1 .74a1.767 1.767 0 001.11 1.44z" />
<path d="M140.737 62.335c-.26-.45-.5-.91-.73-1.36h-.06a.548.548 0 00-.32.44c0 .12.28.47.48.42.2-.05.15-.13.18-.24.075.285.231.542.45.74z" />
<path d="M133.267 38.655c.15.66 1.17 1.07 2 .81a1.092 1.092 0 00.77-1.45 1.339 1.339 0 00-.663-.927 1.33 1.33 0 00-1.137-.073 1.42 1.42 0 00-.97 1.64z" />
<path d="M132.177 33.995a.634.634 0 00-.481.593.642.642 0 00.051.267c.14.58.75 1 1.17.9a1.298 1.298 0 00.84-1.25c-.14-.51-.88-.72-1.58-.51z" />
<path d="M136.147 58.135c.39.08.92-.23.86-.58-.06-.35-.25-1-.66-1-.41 0-.56.56-.63.81a.533.533 0 00.016.496.537.537 0 00.414.274z" />
<path d="M134.407 54.595l.21.07a.72.72 0 00.66.33 1.211 1.211 0 001.35.28 1.665 1.665 0 00.44-1.68c-.19-.43-.37-.89-1-.76-.47.09-1 .11-1.43.16a.692.692 0 00-1 .57.887.887 0 00.168.677.896.896 0 00.602.353z" />
<path d="M132.987 47.085c.45-.12.78-1 .62-1.59a1.005 1.005 0 00-.818-.795.999.999 0 00-.402.015c-.67.2-1.18 1-1 1.49a1.44 1.44 0 001.6.88z" />
<path d="M131.307 39.655a1.252 1.252 0 00-.486 1.604 1.247 1.247 0 001.546.646c.62-.22 1-.85.83-1.31a1.694 1.694 0 00-1.89-.94z" />
<path d="M161.097 8.525l-.11.08.2-.07s-.08-.02-.09-.01z" />
<path d="M192.897 79.255c-.79-.17-1.24 0-1.4.62a1.433 1.433 0 00.85 1.58 1.519 1.519 0 001.54-1c.12-.46-.38-1.06-.99-1.2z" />
<path d="M165.667 84.865a1.998 1.998 0 00-2.15 1.13 1.601 1.601 0 001.09 1.79 1.57 1.57 0 001.84-1 1.583 1.583 0 00-.78-1.92z" />
<path d="M155.637 80.995a1.39 1.39 0 00-1.42 1.12c-.13.7.29 1.22 1.1 1.35a1.119 1.119 0 001.44-1 1.321 1.321 0 00-1.12-1.47z" />
<path d="M192.317 82.555c-.31-.06-.65.61-.69.85-.04.24.28.47.5.55.22.08.63 0 .6-.4-.03-.4-.1-.94-.41-1z" />
<path d="M195.457 80.475c-.17.16-.53.36-.67.67-.14.31.22.93.74 1a.74.74 0 00.83-.67.999.999 0 00-.9-1z" />
<path d="M194.837 86.825c-.3 0-1.06.13-1.11.43-.05.3.56.72.83.86.27.14.72-.2.76-.58.04-.38-.09-.72-.48-.71z" />
<path d="M190.897 88.115c-.16 0-.5-.11-.62.14s.21.48.29.48a.711.711 0 00.49-.21c.05-.06-.08-.4-.16-.41z" />
<path d="M175.157 86.265c-.29-.07-.66.31-.75.77a.457.457 0 00.194.567.455.455 0 00.206.063c.38.06.74 0 .87-.44a.883.883 0 00-.52-.96z" />
<path d="M177.567 81.625c.14.28.53.18.73-.12.067-.112.118-.233.15-.36-.31 0-.62.09-.94.12a.583.583 0 00.06.36z" />
<path d="M176.337 89.995c-.23 0-.64.07-.72.24-.08.17.16.58.34.67.18.09.8 0 .86-.25.06-.25-.34-.66-.48-.66z" />
<path d="M197.317 83.995a.89.89 0 00-1 .56.997.997 0 00.55 1.08.83.83 0 00.94-.57.918.918 0 00-.49-1.07z" />
<path d="M173.297 83.485c-.16 0-.52 0-.59.26-.07.26.3.43.38.42a.72.72 0 00.44-.3c.04-.06-.14-.38-.23-.38z" />
<path d="M186.257 87.785a1.38 1.38 0 00-1.54.77.997.997 0 00.61 1.14 1.19 1.19 0 001.37-.81.814.814 0 00-.44-1.1z" />
<path d="M180.357 83.165a1.352 1.352 0 00-1.73 1 1.718 1.718 0 001.17 2 1.798 1.798 0 001.88-1.17c.23-.82-.32-1.57-1.32-1.83z" />
<path d="M179.427 87.995a.47.47 0 00-.61.21c-.13.31.29.43.45.5.16.07.39-.14.38-.21a.609.609 0 00-.22-.5z" />
<path d="M163.887 81.995c.09 0 .39-.14.39-.21a.648.648 0 00-.22-.5.449.449 0 00-.61.21c-.13.33.29.44.44.5z" />
<path d="M206.957 74.665a.53.53 0 00-.45.36c0 .18-.05.54.15.57a.577.577 0 00.51-.29c.05-.13 0-.62-.21-.64z" />
<path d="M149.617 74.045a1.01 1.01 0 00-.4-.77c-.25-.07-.59.18-.79.26-.15.6.12.81.43.9.31.09.74.01.76-.39z" />
<path d="M162.427 83.195a1.002 1.002 0 00-.33-.73c-.55-.34-1 0-1.34.53.23.47.35 1 1 .93a.708.708 0 00.67-.73z" />
<path d="M194.737 86.155a.705.705 0 00.252-.672.718.718 0 00-.462-.548.734.734 0 00-.657.288.721.721 0 00-.093.712.77.77 0 00.96.22z" />
<path d="M208.207 64.345c-.18 0-.5.29-.52.44a.63.63 0 00.25.5c.13.07.42-.15.47-.29.05-.14-.02-.6-.2-.65z" />
<path d="M139.287 66.145c-.59-.14-1 .06-1.09.57a1.427 1.427 0 00.78 1.71 1.268 1.268 0 001.24-1 1.136 1.136 0 00-.93-1.28z" />
<path d="M198.603 78.538c-.66-.09-1.17.67-1.2.94a1.185 1.185 0 001.26.82.91.91 0 00-.06-1.76z" />
<path d="M201.207 74.995a1.164 1.164 0 00-.544-1.247 1.161 1.161 0 00-.456-.153c-.57-.12-1 .24-1.17 1-.17.76.09 1.15.56 1.23a2.107 2.107 0 001.61-.83z" />
<path d="M131.737 51.935a.514.514 0 00-.18.48.585.585 0 00.49.25c.13 0 .19-.32.32-.57-.29-.1-.5-.21-.63-.16z" />
<path d="M169.577 87.625a.727.727 0 00-.857.202.73.73 0 00-.143.278.838.838 0 00.54 1 .707.707 0 00.815-.213.693.693 0 00.135-.267.852.852 0 00-.49-1z" />
<path d="M205.087 72.315c-.49-.12-.75.52-.92.79-.17.27.08.68.5.8s.77 0 .78-.47c.01-.47.13-1-.36-1.12z" />
<path d="M204.967 75.785a.467.467 0 00-.508.246.468.468 0 00-.052.194.522.522 0 00.42.48.439.439 0 00.53-.3.461.461 0 00-.39-.62z" />
<path d="M141.787 75.055c-.37 0-.91.65-.81.9.1.25.45.37.75.43a.5.5 0 00.63-.54c-.02-.38-.11-.73-.57-.79z" />
<path d="M143.607 70.605a1.646 1.646 0 00-1.43-1.44c-.44 0-.88.51-.88 1.07a1.093 1.093 0 00.265.866 1.1 1.1 0 00.825.374c.62.01 1.22-.42 1.22-.87z" />
<path d="M143.397 78.115a.592.592 0 00-.44.49.509.509 0 00.35.4c.12 0 .39-.18.4-.31.01-.13-.07-.6-.31-.58z" />
<path d="M146.047 78.795a.83.83 0 00-.78.88.797.797 0 00.263.582.785.785 0 00.607.198c.55 0 .9-.23.88-.6a1.336 1.336 0 00-.97-1.06z" />
<path d="M144.397 81.635c-.16 0-.22.18-.23.26-.01.08.17.19.24.19.07 0 .22-.07.22-.19a.239.239 0 00-.23-.26z" />
<path d="M172.107 85.085a.82.82 0 00-1 .62.654.654 0 00.023.58.643.643 0 00.477.33.79.79 0 001-.57.847.847 0 00-.5-.96z" />
<path d="M134.737 59.915a1.106 1.106 0 00-.327-.847 1.105 1.105 0 00-.853-.313c-.71 0-1.26.41-1.24 1a1.608 1.608 0 001.42 1.4c.53.04 1.05-.61 1-1.24z" />
<path d="M133.737 64.995a.87.87 0 00-.75.07.774.774 0 00-.039.906c.06.09.138.166.229.224.24.15.87-.11.91-.65a.666.666 0 00-.35-.55z" />
<path d="M135.917 69.085a.85.85 0 00-.81-1 .91.91 0 00-.9.89.832.832 0 00.237.648.844.844 0 00.643.252.879.879 0 00.83-.79z" />
<path d="M147.467 82.535c-.12 0-.56.17-.56.31 0 .14.36.37.53.41.17.04.35-.21.34-.28-.01-.07-.17-.4-.31-.44z" />
<path d="M139.377 72.425a.757.757 0 00-.71-.81.842.842 0 00-1.016.745.836.836 0 00.866.915 1 1 0 00.86-.85z" />
<path d="M135.837 71.535c-.11 0-.54.1-.56.32-.02.22.36.36.53.4.17.04.34-.1.34-.27a.563.563 0 00-.31-.45z" />
<path d="M140.027 76.905c-.23-.06-1.13.34-1.15.76-.02.42.93.76 1.28.77a.927.927 0 00.81-.73c.03-.41-.58-.71-.94-.8z" />
<path d="M138.947 61.995a1.053 1.053 0 00-.173-1.078 1.05 1.05 0 00-1.037-.342 6.506 6.506 0 00-1.65.59 1.647 1.647 0 00-.58 1.84c.13.36.69 1 1.34.56.59-1.17 1.22-.47 1.92-1.16.081-.126.142-.264.18-.41z" />
<path d="M143.607 68.095c0 .45.64 1.08 1.17 1.12a1 1 0 00.66-.24c-.5-.55-1-1.12-1.43-1.7-.223.22-.364.51-.4.82z" />
<path d="M158.737 79.135a1.37 1.37 0 00-1.52 1.32 1.706 1.706 0 001.52 1.71 1.799 1.799 0 001.63-1.5c.05-.81-.63-1.45-1.63-1.53z" />
<path d="M147.207 70.835a.862.862 0 00-.17 0 .78.78 0 00-.72.9c0 .38.09.81.5.83.71 0 1.13-.32 1.15-.76a1.13 1.13 0 000-.26l-.76-.71z" />
<path d="M158.737 84.075a.447.447 0 00-.56.31c-.07.34.37.37.53.41.16.04.36-.2.34-.27-.02-.07-.21-.41-.31-.45z" />
<path d="M171.547 81.795a.692.692 0 000-.35h-.61a.205.205 0 000 .11c0 .18 0 .54.16.56a.56.56 0 00.45-.32z" />
<path d="M156.007 86.545c-.23 0-.62.19-.66.37-.04.18.26.54.45.59.19.05.78-.17.8-.39.02-.22-.45-.58-.59-.57z" />
<path d="M164.797 84.655a1.19 1.19 0 001.2-1c.01-.096.01-.194 0-.29a.25.25 0 00-.09-.23.698.698 0 00-.56-.45 1.361 1.361 0 00-1.36 1 .938.938 0 00.81.97z" />
<path d="M169.297 84.615a1.306 1.306 0 00-1.37-1.26 1.383 1.383 0 00-1.2 1.35c0 .71.51 1.15 1.33 1.13a1.131 1.131 0 001.24-1.22z" />
<path d="M162.337 85.785c-.16 0-.22.18-.23.26-.01.08.17.19.24.19.07 0 .22-.08.23-.19a.263.263 0 00-.015-.096.254.254 0 00-.225-.163z" />
<path d="M151.017 76.045a1 1 0 00-.94-.88 1.15 1.15 0 00-1.16 1c0 .58.61 1.33 1.1 1.34.49.01.98-.81 1-1.46z" />
<path d="M149.477 78.815c-.2 0-.38.33-.38.44 0 .11.06.54.26.53a.571.571 0 00.44-.37c.03-.15-.06-.62-.32-.6z" />
<path d="M150.957 82.555a.81.81 0 00-.87.79.65.65 0 00.66.8.799.799 0 00.89-.75.827.827 0 00-.68-.84z" />
<path d="M152.167 80.145c-.3 0-.59.42-.6.9-.01.48.17.55.52.54.35-.01.72-.13.77-.59a.866.866 0 00-.69-.85z" />
<path d="M154.527 80.405a.886.886 0 00.53-.65.665.665 0 00-.117-.562.662.662 0 00-.513-.258.73.73 0 00-.77.62c-.08.34.56.97.87.85z" />
<path d="M151.557 78.845c.25.09.86-.43.84-.84-.02-.41-.31-.45-.6-.46-.29-.01-.75.27-.65.64.1.37.15.58.41.66z" />
<path d="M154.987 76.665c-.67-.38-1.34-.78-2-1.2h-.09a.753.753 0 00-.33 1 .791.791 0 001 .4 2.54 2.54 0 01.72-.13c.236.024.474 0 .7-.07z" />
<path d="M182.737 90.355c-.15 0-.25.14-.27.21-.02.07.13.23.2.24a.263.263 0 00.26-.15.27.27 0 00.003-.098.253.253 0 00-.193-.202z" />
</g>
<g id="cross" fill="#00BFB3">
<path d="M176.907 25.395h-7.29v36.46h7.29v-36.46z" />
<path d="M191.487 47.265v-7.29h-36.46v7.29h36.46z" />
</g>
<path
className={dataViewIllustrationDots}
fill="#DAE0EB"
d="M144.467 20.225c13-15 39.55-15.93 53.23-1.19 6.52 7 10.22 17 10.53 26.51.19 4.85-.61 9.688-2.35 14.22-2.3 5.83-6.34 9.82-10.37 14.43a.759.759 0 00.92 1.19c9.84-5.32 14.4-18 14.33-28.61-.07-10.61-4.12-21.65-11.4-29.41-14.57-15.53-42.56-14.37-55.4 2.46-.23.3.27.68.51.4z"
/>
</g>
<g id="arrow" className={dataViewIllustrationDots} fill="#DAE0EB">
<path d="M173.067 190.155a80.5 80.5 0 0038.84-42.56 83.88 83.88 0 006-29.68c.17-9.9-.86-22-6-30.7a.779.779 0 00-1.42.6c1.79 10.21 4.93 19.54 4.8 30.1a81.542 81.542 0 01-5.48 28.38 86.492 86.492 0 01-37.07 43.28c-.38.22 0 .77.34.58h-.01z" />
<path d="M225.047 92.925c-.34-1.93-4-3.06-5.45-3.76-2.45-1.18-4.91-2.63-7.47-3.57-1.76-.65-2.74-.46-3.42 1.4a89.035 89.035 0 00-4.07 13.61.47.47 0 00.86.36 40.588 40.588 0 004.59-8.12c.48-1.08.78-2.56 1.23-3.81.32.15.67.27.9.4l4.91 2.56c1.74.9 4.61 2.54 7.33 2.42.61-.06.69-.95.59-1.49z" />
</g>
<g id="arrow_2" className={dataViewIllustrationDots} fill="#DAE0EB">
<path d="M63.117 87.115c5-11.49 12.33-21.91 22.24-29.7a81.397 81.397 0 0117.09-10.13c6.69-3 14.15-4.33 20.68-7.54a.78.78 0 00-.39-1.45c-7.32.29-15 3.76-21.58 6.77a83.5 83.5 0 00-17 10.26 65.08 65.08 0 00-21.66 31.53c-.12.35.46.61.62.26z" />
<path d="M114.597 31.305a21.137 21.137 0 006.14 7.33c.87.63 1.19.46 1.11.89-.08.43-1.18 1.22-1.42 1.46-1.69 1.74-5.7 5.51-6.17 7.66-.16.7.61 1.4 1.36 1.37 1.72-.07 3.1-2 4.15-3.14 1.48-1.63 3.14-3.2 4.49-5 1-1.31 1.55-2.61.55-4.14-.9-1.35-3-2.31-4.2-3.28a42.109 42.109 0 00-5.38-3.84.469.469 0 00-.64.64l.01.05z" />
</g>
<g id="paper">
<path fill="#F9B110" d="M175.737 199.325h-23.87v-17.82l11.69-11.69h12.18v29.51z" />
<path fill="#FEC514" d="M163.557 169.815v11.69h-11.69l11.69-11.69z" />
</g>
<g id="paper_2">
<path fill="#F9B110" d="M86.517 78.835h-32.67v-24.37l16-16h16.67v40.37z" />
<path fill="#FEC514" d="M81.857 68.465h-22.67v3.33h22.67v-3.33z" />
<path fill="#FEC514" d="M81.857 61.125h-22.67v3.33h22.67v-3.33z" />
<path fill="#FEC514" d="M69.847 38.465v16h-16l16-16z" />
</g>
<g id="database-top">
<path
fill="#07C"
d="M159.707 92.085H39.217c-5.666 0-10.26 4.594-10.26 10.26v25.26c0 5.666 4.593 10.26 10.26 10.26h120.49c5.666 0 10.26-4.594 10.26-10.26v-25.26c0-5.666-4.594-10.26-10.26-10.26z"
/>
<path
id="circle_2"
fill="#F04E98"
d="M61.007 123.085a8.91 8.91 0 008.91-8.91 8.91 8.91 0 10-8.91 8.91z"
/>
<g fill="#343741" className={dataViewIllustrationVerticalStripes}>
<path d="M145.737 100.495h-6v29h6v-29z" />
<path d="M133.737 100.495h-6v29h6v-29z" />
</g>
<g id="database-top-dots" fill="#006ABB">
<path d="M29.557 109.405s.06 0 .08-.08a1.09 1.09 0 00-.5-1.47 1.082 1.082 0 00-.18 0v2.14c.231-.162.434-.361.6-.59z" />
<path d="M38.797 135.065a1.798 1.798 0 00-2.13-.92 1.679 1.679 0 00-.68.94.623.623 0 00-.41-.26h-.06a1.43 1.43 0 00.17-.74 1.251 1.251 0 00-.19-.54c.366.074.746.002 1.06-.2.49-.29.79-1 .57-1.37a1.525 1.525 0 00-1.73-.59.873.873 0 00-.19.15c.01-.09.01-.18 0-.27a.46.46 0 00.23-.6c-.1-.29-.2-1-.7-.94a.478.478 0 00-.32.15 5.326 5.326 0 00-.52-.38 1.16 1.16 0 01-.45-.51 1.073 1.073 0 00-1.204-.816 1.072 1.072 0 00-.486.206.557.557 0 00-.09-.3.936.936 0 00-1.21-.08 1.001 1.001 0 00-.44 1.31.703.703 0 00.43.31 1 1 0 00-.06.36 1.128 1.128 0 001.17 1.16c.174-.021.343-.072.5-.15a.993.993 0 010 .26c0 .66.56 1 1 1.31a1.505 1.505 0 001.63-.25l.11-.12a1.43 1.43 0 00.09.92 1.999 1.999 0 00-1.1-.16 1.229 1.229 0 00-1.1.78.69.69 0 00-.59.07c-.256.14-.478.335-.65.57.44.501.929.957 1.46 1.36a.895.895 0 00.25-.4 1.616 1.616 0 001.25.39c.116-.022.227-.063.33-.12a.602.602 0 00.49.55.732.732 0 00.62-.11c.026.119.07.233.13.34a1.723 1.723 0 002.15.46 1.369 1.369 0 00.67-1.77z" />
<path d="M36.837 137.455a.733.733 0 00-.28 0l.59.15a.552.552 0 00-.31-.15z" />
<path d="M28.957 123.605v2a.699.699 0 00.41 0 1.121 1.121 0 00.544-1.184 1.13 1.13 0 00-.174-.426.8.8 0 00-.78-.39z" />
<path d="M32.077 132.665a.92.92 0 00-1.23-.3.687.687 0 00-.19.19.855.855 0 00-.16 0 .81.81 0 00-.22.07c.34.599.735 1.165 1.18 1.69a2.27 2.27 0 00.43-.58.84.84 0 00.19-1.07z" />
<path d="M30.377 123.635a1.76 1.76 0 00.09 1.51c.24.55 0 1.12.45 1.53a2.626 2.626 0 002.33.64.59.59 0 000 .86c.23.23.63.38.84 0a.9.9 0 000-.87.873.873 0 00-.55-.15 2.227 2.227 0 001.41-1.73c.06-.7-.78-1-1-1.73a1.86 1.86 0 00-.82-1.13.84.84 0 00.28-.51c.06-.11.12-.21.19-.36-.32-.35-.56-.88-1.19-.61a.79.79 0 00-.455.397.784.784 0 00-.035.603c.051.126.126.242.22.34-.372.05-.732.168-1.06.35a1.733 1.733 0 00-.7.86z" />
<path d="M35.427 127.785a.818.818 0 00.615-.211.822.822 0 00.275-.589.684.684 0 00.8-.33 1.003 1.003 0 00.08-.8c-.31-.57-.82-.49-1.43-.21v.47a.75.75 0 00-.22-.08.91.91 0 00-.77.42.997.997 0 00.285 1.135c.107.089.231.156.365.195z" />
<path d="M29.327 125.995a.937.937 0 00-.37-.33v2.47a2.39 2.39 0 00.28-.11c.56-.29.5-1.26.09-2.03z" />
<path d="M39.177 134.865a.534.534 0 00.38-.43c0-.15-.19-.59-.39-.55a.537.537 0 00-.32.49c.04.18.13.53.33.49z" />
<path d="M29.927 127.575a.56.56 0 00.57 0c.12-.09.32-.54.15-.66a.518.518 0 00-.58.09c-.1.15-.33.45-.14.57z" />
<path d="M43.027 124.805a.755.755 0 00-.22-.36c-.06-.18-.23-.45-.38-.43-.15.02-.14.08-.21.17-.07.09 0 0-.08 0-.48.13-.67 1.2-.34 1.45s1.32-.35 1.23-.83z" />
<path d="M42.427 132.135c-.07-.25-.42-.61-.62-.6-.2.01-.5.36-.67.49a.239.239 0 000 .09 1.136 1.136 0 00-1 .18c-.58.37-.79.94-.49 1.37a1.66 1.66 0 002 .48 1.21 1.21 0 00.17-1.4c.35-.02.72-.22.61-.61z" />
<path d="M36.737 100.125a1.451 1.451 0 00.24 1.71 1.466 1.466 0 001.52-.28 1 1 0 00-.11-1.2c-.65-.55-1.28-.64-1.65-.23z" />
<path d="M38.657 103.615a1.314 1.314 0 00-.78.09 1.721 1.721 0 00-.86 1.24 1.288 1.288 0 00.6 1.22 1.132 1.132 0 001.13 0 1.364 1.364 0 00.71-1c0-.47.17-1-.12-1.26a.818.818 0 00-.68-.29z" />
<path d="M40.237 107.995c-.61.34-.52.74-.93.93-.41.19-.45.49-.36 1 .09.51.24 1.2.91 1.06.67-.14.65-.6.59-.79-.06-.19.14-.58.28-.39.14.19.42.22.89-.11a.855.855 0 00.449-.537.861.861 0 00-.1-.693 1.257 1.257 0 00-1.26-.63 1.262 1.262 0 00-.47.16z" />
<path d="M42.417 112.445a.739.739 0 00-1.17.51.771.771 0 00.74.65c.42-.03.52-.29.43-1.16z" />
<path d="M42.737 101.565c-.49-.42-1.06-.31-1.59.32a1.17 1.17 0 00.08 1.4 1.924 1.924 0 001.93 0 1.438 1.438 0 00-.42-1.72z" />
<path d="M39.677 126.275c.13-.1.3-.33.21-.53s-.4-.21-.55-.16c-.15.05-.45.35-.3.6.15.25.53.16.64.09z" />
<path d="M41.537 129.895a.78.78 0 00-.24 1 .597.597 0 00.42.324.597.597 0 00.51-.144c.155-.232.27-.489.34-.76-.31-.48-.71-.61-1.03-.42z" />
<path d="M42.337 118.285a6.229 6.229 0 00-.54-1 3.5 3.5 0 00.21-.48 1.65 1.65 0 00-.44-1.79 1.904 1.904 0 00-1.88-.14 1 1 0 00-.48.54 1.52 1.52 0 00.88 2l.5.22c-.15.32-.32.61-.43.91-.045.133-.075.27-.09.41-.1.05-.12.19-.12.33l-.61-.1a5.173 5.173 0 00-.22-.49 1.687 1.687 0 00-1.64-.85 1.788 1.788 0 00-.73.4.56.56 0 00-.17-.26.665.665 0 00-.74.12c-.2.23.2.68.35.74.15.06.06 0 .09 0l-.09.2a1.51 1.51 0 00-.27 1.72c.19.182.43.304.69.35a1.158 1.158 0 00-.84.13 1.41 1.41 0 00-.45 1.84 1.552 1.552 0 001.72.78 1.21 1.21 0 00-.13.58c0 .66.63 1.44 1.12 1.43a1.486 1.486 0 001.31-1.14 1.198 1.198 0 001.13-.48.74.74 0 00-.24-1c.05-.16.08-.32 0-.4-.08-.08-.57 0-.67.12a.65.65 0 00-.07.45 2.89 2.89 0 00-.28.59 1.693 1.693 0 00-1.3-.71 1.112 1.112 0 00.12-1.27 2.076 2.076 0 00-1.34-.89 1.433 1.433 0 001.14-.27.829.829 0 00.17-.27c.1 0 .18 0 .28-.08l.49-.22c.14.32.25.63.41.92a.796.796 0 00.48.501.8.8 0 00.69-.071 1.244 1.244 0 00.86-1.16.998.998 0 00-.19-.63 1.204 1.204 0 001-.27 1.11 1.11 0 00.32-1.31z" />
<path d="M35.737 116.705a.773.773 0 001-.13c.25-.33.12-.58-.59-1.08a.731.731 0 00-.41 1.21z" />
<path d="M36.857 113.135a.625.625 0 00.22 0 .47.47 0 00.32.09c.16 0 .43-.07.42-.34a.27.27 0 00-.2-.29c.1-.49-.23-.83-.69-1.07-.45.16-.91.37-.88.94a.728.728 0 00.81.67z" />
<path d="M40.257 131.765c.17 0 .53-.1.58-.31.05-.21-.09-.23-.21-.3a.778.778 0 000-.16c.08-.8-.14-1.13-.85-1.2.226-.465.349-.973.36-1.49a1.466 1.466 0 00-.11-.43 1.048 1.048 0 00-1.91-.13 6.07 6.07 0 00-.64 1.31c-.09-.63-.33-.65-1.14-.44-.25.6 0 1 .58 1.24-.28 1.52.46 2 2 1.5.086.059.163.129.23.21a.736.736 0 00.508.362.732.732 0 00.602-.162z" />
<path d="M62.917 132.575c.52.38.95.24 1.34-.26 1.41.64 2.08.05 2-1.58.079-.068.166-.125.26-.17.43-.17.49-.5.44-.9a.788.788 0 00-.63-.67c-.84-.3-1.21-.11-1.45.75-.18.66-.39 1.3-.58 1.94-1.27-.27-1.38-.18-1.38.89z" />
<path d="M63.247 136.405c-.43-.07-1.28.81-1.33 1.37v.09h2.24a1.379 1.379 0 00-.91-1.46z" />
<path d="M49.287 132.665c-.13.16-.55.21-.48 0 .07-.21 0-.48-.59-.68a.866.866 0 00-1.01.118.871.871 0 00-.21.292 1.27 1.27 0 00.62 1.67c.64.31.91 0 1.3.24s.66.1 1-.24c.09-.09.19-.19.28-.3a.996.996 0 001.14.21 1.369 1.369 0 00.64-1.68 1.477 1.477 0 00-1.75-.51 1.007 1.007 0 00-.39.68.577.577 0 00-.55.2z" />
<path d="M65.597 126.685a.72.72 0 00-.78-.63.997.997 0 00-.71.38c-.29.58.06 1 .63 1.3.45-.27 1-.42.86-1.05z" />
<path d="M75.137 135.995a1.01 1.01 0 00.69-.39 1.001 1.001 0 00.19-.77 1.556 1.556 0 00-.8-1 .518.518 0 00-.4-.23.846.846 0 00-.92.61v.06a1.267 1.267 0 00-.28 1c.12.47.89.86 1.52.72z" />
<path d="M53.737 137.175a1.184 1.184 0 00-.768-.651 1.194 1.194 0 00-.992.171 1.258 1.258 0 00-.17.15.612.612 0 00-.24-.07c-.32-.05-.66 0-.78.38a.759.759 0 00.27.71h2.82a1.21 1.21 0 00-.14-.69z" />
<path d="M44.247 106.875c.3.49.49 1.12 1.16 1.35l.75-.32c.67.39 1.4.44 1.67.09a.768.768 0 000-1c-.41-.57-.7-.62-1.47-.31-.472-.239-.96-.446-1.46-.62a.54.54 0 00-.65.81z" />
<path d="M51.177 130.395a.77.77 0 00.12-1c-.26-.33-.54-.26-1.2.31a.74.74 0 001.08.69z" />
<path d="M45.477 96.605a.65.65 0 00-.12 1 .738.738 0 001.06 0 .65.65 0 00-.07-1 .6.6 0 00-.87 0z" />
<path d="M46.167 103.905c.14.17.53 0 .56-.14a.81.81 0 000-.55.562.562 0 00-.45 0c-.13.18-.25.53-.11.69z" />
<path d="M81.077 129.885c0-.15-.27-.5-.47-.49-.2.01-.32.48-.28.61.04.13.29.39.45.35a.553.553 0 00.3-.47z" />
<path d="M49.927 126.125c-.12.07-.12.35-.19.63.28 0 .52.1.65 0a.503.503 0 00.07-.5.576.576 0 00-.53-.13z" />
<path d="M79.787 133.515a1.176 1.176 0 00-1.048 1.021 1.18 1.18 0 00.048.519c.17.63.77 1.08 1.25.93a1.696 1.696 0 001.07-1.82 1.178 1.178 0 00-1.32-.65z" />
<path d="M44.407 116.725a.781.781 0 00.74.66.709.709 0 00.42-1.17.737.737 0 00-1.019.162.74.74 0 00-.141.348z" />
<path d="M45.567 111.995a.305.305 0 00.102-.23.313.313 0 00-.102-.23c-.12-.11-.45-.36-.63-.2-.18.16 0 .52.08.57.08.05.34.28.55.09z" />
<path d="M46.737 128.735c.2.38.66.19.81.1.15-.09.18-.37.25-.59.24.05.43-.29.43-.38 0-.09 0-.44-.23-.5a.299.299 0 00-.13-.008.298.298 0 00-.121.046.311.311 0 00-.139.212.91.91 0 00.06.53c-.18-.08-.37-.18-.51-.13-.14.05-.67.34-.42.72z" />
<path d="M80.987 137.865h2.55a2.067 2.067 0 000-.55 1.43 1.43 0 00-1.53-1.12c-.61.09-1.06.87-1.02 1.67z" />
<path d="M46.987 136.305a1.336 1.336 0 00-1.25 0c-.37.27-.9.42-1 .81a.822.822 0 00.16.71v.05h3a1.466 1.466 0 00-.41-.25c.02-.129.02-.261 0-.39a1.13 1.13 0 00-.5-.93z" />
<path d="M99.737 135.055c.3-.38-.44-1.27-1-1.2-.56.07-1 .67-.84 1.06.16.39 1.51.52 1.84.14z" />
<path d="M89.617 136.305c.39-.09.88-.75.79-1.06a1.202 1.202 0 00-1.23-.69.842.842 0 00-.53 1.05.931.931 0 00.97.7z" />
<path d="M81.847 132.335c.2 0 .23-.45.21-.61-.02-.16-.26-.18-.39-.26a1.226 1.226 0 00-.26.49c0 .1.23.42.44.38z" />
<path d="M92.997 137.575c-.09-.48-1-1.19-1.45-1.09-.61.14-.85.63-.66 1.37h2.09a.71.71 0 00.02-.28z" />
<path d="M97.737 136.325a.768.768 0 00-.48-.28c-.06 0-.26.31-.22.39.04.08.28.46.5.43.22-.03.2-.47.2-.54z" />
<path d="M91.737 133.995a.715.715 0 00.489-.3.727.727 0 00.12-.56.633.633 0 00-.306-.427.644.644 0 00-.523-.053.594.594 0 00-.402.248.6.6 0 00-.098.462.643.643 0 00.453.607.635.635 0 00.267.023z" />
<path d="M29.097 112.655h-.14v3a2.188 2.188 0 001.1-1.28 1.501 1.501 0 00-.96-1.72z" />
<path d="M70.597 136.785c-.09.59.14 1 .66 1.05.74.12 1.58-.35 1.64-.92.06-.57-.52-1.09-1.11-1.16a1.15 1.15 0 00-1.19 1.03z" />
<path d="M70.557 132.705a.498.498 0 00-.5.253.511.511 0 00-.045.383.498.498 0 00.238.303.495.495 0 00.187.061.51.51 0 00.45-.45.44.44 0 00-.33-.55z" />
<path d="M69.317 130.995c-.14 0-.62 0-.63.26a.537.537 0 00.4.42c.18 0 .55 0 .55-.2a.55.55 0 00-.32-.48z" />
<path d="M72.347 132.825c.073.09.157.17.25.24 0-.08.19-.12.17-.25-.02-.13-.18-.17-.26-.17-.08 0-.17.16-.16.18z" />
<path d="M69.467 136.585c-.13.19-.38.39-.37.57.01.18.13.7.56.61.43-.09.38-.58.33-.75-.05-.17-.33-.29-.52-.43z" />
<path d="M84.897 136.065a1.303 1.303 0 001.18.93c.5-.1.8-.82.63-1.54a.632.632 0 00-.82-.5c-.59.11-1.09.68-.99 1.11z" />
<path d="M68.397 133.445a.526.526 0 00-.28.47c0 .13.3.21.54.36.1-.26.24-.47.2-.62a.511.511 0 00-.46-.21z" />
<path d="M78.147 137.085a1.37 1.37 0 00-.33.78h2.22a1.346 1.346 0 00-.14-.36c-.21-.36-.43-1.22-1-1.21-.57.01-.56.56-.75.79z" />
<path d="M65.197 134.815c.09-.45-.09-.77-.53-.74-.44.03-1-.06-1.09.44-.09.5.58.71.86.86.28.15.68-.14.76-.56z" />
<path d="M73.027 130.995a.67.67 0 00-.16-.33c-.08-.49-.75-.53-1-.76l-.23.14v1.32l.3.18a1.83 1.83 0 01.47-.25.358.358 0 00.21.19c.27.08.35-.35.41-.49z" />
<path d="M86.517 129.795c-.16.38-.73.8-.49 1.18.24.38.94.56 1.23.1.13-.21-.16-.7-.3-1.05-.04-.09-.22-.12-.44-.23z" />
<path d="M51.427 124.995a.733.733 0 00.71.76.737.737 0 00.37-.09.769.769 0 00.12-1 .708.708 0 00-.698-.19.716.716 0 00-.502.52z" />
<path d="M47.997 103.135c-.23.36.42 1.56.9 1.59.48.03.81-1.08.45-1.48-.36-.4-1.11-.46-1.35-.11z" />
<path d="M45.227 92.185a.3.3 0 00.19-.1h-.54a.68.68 0 00.35.1z" />
<path d="M101.017 137.245c-.55 0-.79.16-1 .62h1.75a.272.272 0 000-.09.739.739 0 00-.75-.53z" />
<path d="M67.797 127.645c-.27 0-.7.26-.74.46-.04.2.23.57.32.77.61.1.8-.19.86-.51.06-.32-.04-.73-.44-.72z" />
<path d="M56.197 125.995a1.102 1.102 0 00-1.23.5 4.81 4.81 0 00-.49 1 4.837 4.837 0 00-.52.1 1.66 1.66 0 00-1.22 1.4 1.9 1.9 0 00.95 1.63.931.931 0 00.72.09 1.503 1.503 0 00.935-.684 1.51 1.51 0 00.175-1.146c0-.18-.06-.35-.09-.54.35-.06.67-.08 1-.16.6-.16.78-.43.71-1a1.25 1.25 0 00-.94-1.19z" />
<path d="M66.487 136.145a.655.655 0 000 .14.494.494 0 00-.28 0c-.6.08-.71.52-.67 1.06a1.163 1.163 0 001.42.08c.17.13.369.216.58.25a1.65 1.65 0 001.79-1.41 1.69 1.69 0 00-1.29-1.56c-.76-.16-1.41.46-1.55 1.44z" />
<path d="M61.107 137.355c-.15.06-.23.29-.27.51h1c-.1-.25-.52-.58-.73-.51z" />
<path d="M60.877 125.845c.06-.13-.19-.4-.33-.45-.14-.05-.59.07-.63.26-.04.19.33.47.47.48a.625.625 0 00.49-.29z" />
<path d="M58.077 129.995a.997.997 0 00.91 1.15c.64.14 1.54-.26 1.65-.74a1.462 1.462 0 00-1-1.63 1.49 1.49 0 00-1.56 1.22z" />
<path d="M55.977 136.285c.08.397.3.752.62 1a1.816 1.816 0 001.49.29c.59-.1 1.09.26 1.59-.07a2.508 2.508 0 001.1-2.8 1.995 1.995 0 00-1.26-1.47c-.67-.23-1.19.5-1.93.55-1.73.06-1.85 1.36-1.61 2.5z" />
<path d="M44.867 130.515a1.37 1.37 0 00.06-.88l-.46-.29a.698.698 0 00-.13-.54.507.507 0 00-.19-.13c-.15-.51-.45-.89-.78-.9a.761.761 0 00-.77.49c-.56-.08-.83.33-1 .93.38.37.65.83 1.23.57a.857.857 0 00.22-.15.931.931 0 00.11.09c.074.523.18 1.041.32 1.55a1.831 1.831 0 00-.18.82c0 1.21 1.24 1 1 1.73s-.83.17-1.15 1.25v.21c-.2-.5-.63-1-.93-1-.54 0-1.14.61-1.22 1.34a.8.8 0 000 .27.676.676 0 00-.26.1.611.611 0 00-.25.38.679.679 0 00-.49-.42 1.738 1.738 0 00-1.54.94.84.84 0 00.58 1h1.7a.874.874 0 00.07-.12l.15.12h1.16a1.372 1.372 0 00.44-1.27c.257-.106.465-.306.58-.56a.996.996 0 00.79.812.993.993 0 00.4-.002c.76-.22.87-.92 1.06-.92.19 0 1 .24 1.17-.37.35-1.29-.88-1.31-.89-1.72-.01-.41.45-1.11.48-1.72.01-.85-.41-1.62-1.28-1.61zm-4.44 6.8v-.13a.746.746 0 00.06.16l-.06-.03z" />
<path d="M52.207 129.855a1.517 1.517 0 00-.16.31c.09 0 .17.14.29.09.12-.05.11-.22.09-.3-.02-.08-.2-.12-.22-.1z" />
<path d="M32.307 118.465a1.994 1.994 0 001.92-.22c.56-.41.26-1.26.64-1.89.9-1.49-.09-2.36-1.19-2.78a1.863 1.863 0 00-.39-.09 1 1 0 00-.56-.35 1.264 1.264 0 00-1.46 1 1.24 1.24 0 000 .71c-.29.37-.68.61-.68 1.1a2.49 2.49 0 001.72 2.52z" />
<path d="M28.957 102.995v2.75a1.15 1.15 0 00.48-.37c.71-1 .54-1.31-.23-2.18a.996.996 0 00-.25-.2z" />
<path d="M29.737 117.995c-.11.075-.205.17-.28.28a.358.358 0 00-.08-.12 1.282 1.282 0 00-.33-.5.52.52 0 00-.13-.06v2.17h.06a1.36 1.36 0 00.11.63c.17.278.437.482.75.57l-.23.13c-.32.17-.27.64 0 .94.27.3.69.36.92 0 .23-.36.52-.7.32-1.05a1.18 1.18 0 00.35-.15 1.37 1.37 0 00.52-.63l.06.07a.725.725 0 00.848.074.728.728 0 00.222-.204.992.992 0 00.16-.85.883.883 0 00-1.14-.36 1.5 1.5 0 00-.1-.22 1.579 1.579 0 00-2.03-.72z" />
<path d="M31.327 104.055a1.14 1.14 0 00.16.87 1.472 1.472 0 001.74.57c.45.19 1.11.25 1.34 0 .23-.25-.28-1.31-.81-1.31a1.22 1.22 0 00-1-1 1.351 1.351 0 00-1.43.87z" />
<path d="M31.017 108.785a1.717 1.717 0 001.84.76.519.519 0 00.25.89 2.431 2.431 0 001.78 0c.06-.2.13-.41.2-.62a.553.553 0 00.68-.16.869.869 0 00.26-.51.791.791 0 00.28-.68.75.75 0 00-.71-.63c-.7-.07-.93.11-1.21.89l-1 .48a2.001 2.001 0 00.13-2.15 1.399 1.399 0 00-1.09-.56.718.718 0 00-.44-.33s-.29.27-.26.36a.211.211 0 000 .1l-.18.08a1.373 1.373 0 00-.762.913 1.382 1.382 0 00.232 1.167z" />
<path d="M35.307 96.365a1.39 1.39 0 00.31 1.39 1.18 1.18 0 001.25-.39.62.62 0 00-.27-.9c-.6-.34-1.1-.38-1.29-.1z" />
<path d="M34.917 93.495c-.54-.23-.86.12-1.06.57a.932.932 0 001.3.65c.18-.48.33-.98-.24-1.22z" />
<path d="M35.567 103.125c.05-.15 0-.48-.25-.52a.418.418 0 00-.49.23.61.61 0 00.24.61c.18.08.43-.14.5-.32z" />
<path d="M34.487 106.135a.315.315 0 00-.164.232.308.308 0 00.094.268.352.352 0 00.366.121.353.353 0 00.256-.289.35.35 0 00-.552-.332z" />
<path d="M33.537 101.585a1.857 1.857 0 001.83 0 1.067 1.067 0 00.254-.805 1.057 1.057 0 00-.414-.735c-.51-.51-.91-.49-1.48.1s-.58 1.09-.19 1.44z" />
<path d="M35.927 92.635a1.33 1.33 0 002 0c.127-.128.226-.281.29-.45-.794.074-1.576.242-2.33.5l.04-.05z" />
<path d="M38.557 96.505c-.1.14 0 .5.12.68.258.338.55.65.87.93.27.26.53.19.79-.12v-.38a1.76 1.76 0 01-.37-1.08c0-.18-.43-.42-.67-.43a1 1 0 00-.74.4z" />
<path d="M39.737 92.995a1.3 1.3 0 00-.1 1.5c.36.36 1.12.2 1.62-.34a.632.632 0 000-1 1.12 1.12 0 00-1.52-.16z" />
<path d="M41.887 100.475a.847.847 0 001.17.1 1.76 1.76 0 00.09-1.81c-.46-.42-1-.35-1.5.22s-.33 1.01.24 1.49z" />
<path d="M30.887 99.555l.12.13a1.6 1.6 0 002-.25 1.55 1.55 0 00-1.76-2.44 1.49 1.49 0 00-.61-.25 10.355 10.355 0 00-1.27 2.76 1.23 1.23 0 001.52.05z" />
<path d="M53.667 134.995a.741.741 0 00.15-1.07.993.993 0 00-.8-.36c-.58.15-.68.67-.62 1.25.39.26.82.52 1.27.18z" />
<path d="M32.477 94.735a.52.52 0 00.17-.27l-.48.44a.68.68 0 00.31-.17z" />
<path d="M42.247 95.875a1 1 0 000 1.2 1.241 1.241 0 001.32.06 1.2 1.2 0 00-.12-1.41.85.85 0 00-1.2.15z" />
<path d="M37.017 93.625a1.18 1.18 0 00-.12 1.3.619.619 0 00.94.09c.54-.42.77-.87.58-1.15a1.38 1.38 0 00-1.4-.24z" />
<path d="M45.927 124.665a1.172 1.172 0 00.39 1.43c.73-.37 1-.71.82-1.1-.26-.55-.71-.53-1.21-.33z" />
<path d="M43.647 120.435a.805.805 0 00-.23.71.516.516 0 00.572.37.513.513 0 00.228-.09c.19-.13.52-.77.36-1-.16-.23-.77-.1-.93.01z" />
</g>
</g>
<g id="stars">
<path
fill="#00BFB3"
d="M35.737 82.395c-10.83 0-17.41 5.69-17.41 17.41 0-11.72-4.79-17.41-17.41-17.41 12.62 0 17.43-5.2 17.41-17.41-.02 12.21 6.52 17.41 17.41 17.41z"
/>
<path
fill="#7DE2D1"
d="M97.367 13.075c-7.86 0-12.63 4.13-12.63 12.64 0-8.51-3.48-12.64-12.64-12.64 9.15 0 12.64-3.78 12.64-12.65 0 8.87 4.74 12.65 12.63 12.65z"
/>
<path
fill="#7DE2D1"
d="M193.597 151.365c-7.86 0-12.64 4.12-12.64 12.64 0-8.52-3.48-12.64-12.64-12.64 9.16 0 12.66-3.78 12.64-12.65 0 8.87 4.78 12.65 12.64 12.65z"
/>
</g>
</g>
</svg>
);
};

View file

@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FC } from 'react';
import React from 'react';
import { EuiButton, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { FileAnalysis } from './file_manager/file_wrapper';
import type { UploadStatus } from './file_manager/file_manager';
import { CLASH_TYPE } from './file_manager/merge_tools';
interface Props {
uploadStatus: UploadStatus;
filesStatus: FileAnalysis[];
removeClashingFiles: () => void;
}
export const FileClashWarning: FC<Props> = ({ uploadStatus, filesStatus, removeClashingFiles }) => {
const fileClashes = uploadStatus.fileClashes;
const clashType = fileClashes.some((fileClash) => fileClash.clashType === CLASH_TYPE.FORMAT)
? CLASH_TYPE.FORMAT
: fileClashes.some((fileClash) => fileClash.clashType === CLASH_TYPE.MAPPING)
? CLASH_TYPE.MAPPING
: CLASH_TYPE.UNSUPPORTED;
const { title, description } =
clashType === CLASH_TYPE.MAPPING
? {
title: i18n.translate('xpack.dataVisualizer.file.fileClashWarning.mappingClashTitle', {
defaultMessage: 'Incompatible mapping',
}),
description: i18n.translate(
'xpack.dataVisualizer.file.fileClashWarning.mappingClashDescription',
{
defaultMessage: 'Mappings in the selected files are not compatible with each other',
}
),
}
: clashType === CLASH_TYPE.FORMAT
? {
title: i18n.translate('xpack.dataVisualizer.file.fileClashWarning.fileFormatClashTitle', {
defaultMessage: 'Incompatible file formats',
}),
description: i18n.translate(
'xpack.dataVisualizer.file.fileClashWarning.fileFormatClashDescription',
{
defaultMessage:
'The selected files must have the same format. e.g. all CSV or all log files',
}
),
}
: {
title: i18n.translate(
'xpack.dataVisualizer.file.fileClashWarning.fileFormatNotSupportedTitle',
{
defaultMessage: 'File format not supported',
}
),
description: i18n.translate(
'xpack.dataVisualizer.file.fileClashWarning.fileFormatNotSupportedDescription',
{
defaultMessage: 'Some of the selected files are not supported for upload.',
}
),
};
return (
<EuiCallOut title={title} color="danger">
<p>{description}</p>
<EuiButton onClick={() => removeClashingFiles()} color="danger" size="s" fill>
<FormattedMessage
id="xpack.dataVisualizer.file.fileClashWarning.deleteAllButtonLabel"
defaultMessage="Delete all"
/>
</EuiButton>
</EuiCallOut>
);
};

View file

@ -0,0 +1,474 @@
/*
* 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 { FileUploadStartApi } from '@kbn/file-upload-plugin/public/api';
import type { Subscription } from 'rxjs';
import type { Observable } from 'rxjs';
import { switchMap, combineLatest, BehaviorSubject, of } from 'rxjs';
import type { HttpSetup } from '@kbn/core/public';
import type { IImporter } from '@kbn/file-upload-plugin/public/importer/types';
import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types';
import type { ImportResponse, IngestPipeline } from '@kbn/file-upload-plugin/common/types';
import type {
IndicesIndexSettings,
MappingTypeMapping,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { i18n } from '@kbn/i18n';
import type { FileUploadResults } from '@kbn/file-upload-common';
import type { FileAnalysis } from './file_wrapper';
import { FileWrapper } from './file_wrapper';
import {
createKibanaDataView,
getInferenceId,
} from '../../application/file_data_visualizer/components/import_view/import';
import { AutoDeploy } from '../../application/file_data_visualizer/components/import_view/auto_deploy';
import type { FileClash } from './merge_tools';
import { createMergedMappings, getFormatClashes, getMappingClashInfo } from './merge_tools';
export enum STATUS {
NA,
NOT_STARTED,
STARTED,
COMPLETED,
FAILED,
}
export interface UploadStatus {
analysisOk: boolean;
overallImportStatus: STATUS;
indexCreated: STATUS;
pipelineCreated: STATUS;
modelDeployed: STATUS;
dataViewCreated: STATUS;
pipelinesDeleted: STATUS;
fileImport: STATUS;
filesStatus: FileAnalysis[];
fileClashes: FileClash[];
formatMix: boolean;
errors: Array<{ title: string; error: any }>;
}
export class FileManager {
private readonly files$ = new BehaviorSubject<FileWrapper[]>([]);
private readonly analysisValid$ = new BehaviorSubject<boolean>(false);
public readonly fileAnalysisStatus$: Observable<FileAnalysis[]> = this.files$.pipe(
switchMap((files) =>
files.length > 0 ? combineLatest(files.map((file) => file.fileStatus$)) : of([])
)
);
private mappingsCheckSubscription: Subscription;
private settings;
private mappings: MappingTypeMapping | null = null;
private pipelines: IngestPipeline[] | null = null;
private inferenceId: string | null = null;
private importer: IImporter | null = null;
private timeFieldName: string | undefined | null = null;
private commonFileFormat: string | null = null;
public readonly uploadStatus$ = new BehaviorSubject<UploadStatus>({
analysisOk: false,
overallImportStatus: STATUS.NOT_STARTED,
indexCreated: STATUS.NOT_STARTED,
pipelineCreated: STATUS.NOT_STARTED,
modelDeployed: STATUS.NA,
dataViewCreated: STATUS.NA,
pipelinesDeleted: STATUS.NOT_STARTED,
fileImport: STATUS.NOT_STARTED,
filesStatus: [],
fileClashes: [],
formatMix: false,
errors: [],
});
private autoAddSemanticTextField: boolean = false;
constructor(
private fileUpload: FileUploadStartApi,
private http: HttpSetup,
private dataViewsContract: DataViewsServicePublic,
private autoAddInferenceEndpointName: string | null = null,
private removePipelinesAfterImport: boolean = true,
indexSettingsOverride: IndicesIndexSettings | undefined = undefined
) {
this.autoAddSemanticTextField = this.autoAddInferenceEndpointName !== null;
this.settings = indexSettingsOverride ?? {};
this.mappingsCheckSubscription = this.fileAnalysisStatus$.subscribe((statuses) => {
const allFilesAnalyzed = statuses.every((status) => status.loaded);
if (allFilesAnalyzed) {
this.analysisValid$.next(true);
const uploadStatus = this.uploadStatus$.getValue();
if (uploadStatus.fileImport === STATUS.STARTED) {
return;
}
if (this.getFiles().length === 0) {
this.setStatus({
fileClashes: [],
});
return;
}
const { formatsOk, fileClashes } = this.getFormatClashes();
const { mappingClashes, mergedMappings } = this.createMergedMappings();
const mappingsOk = mappingClashes.length === 0;
if (formatsOk === false) {
this.setStatus({
fileClashes,
});
} else if (mappingsOk) {
this.mappings = mergedMappings;
this.pipelines = this.getPipelines();
this.addSemanticTextField();
this.setStatus({
fileClashes: [],
});
} else {
this.setStatus({
fileClashes: getMappingClashInfo(mappingClashes, statuses),
});
}
this.setStatus({
analysisOk: mappingsOk && formatsOk,
});
}
});
}
destroy() {
this.files$.complete();
this.mappingsCheckSubscription.unsubscribe();
}
private setStatus(status: Partial<UploadStatus>) {
this.uploadStatus$.next({
...this.uploadStatus$.getValue(),
...status,
});
}
async addFiles(fileList: FileList) {
this.setStatus({
analysisOk: false,
});
const promises = Array.from(fileList).map((file) => this.addFile(file));
await Promise.all(promises);
}
async addFile(file: File) {
const f = new FileWrapper(file, this.fileUpload);
const files = this.getFiles();
files.push(f);
this.files$.next(files);
await f.analyzeFile();
}
async removeFile(index: number) {
const files = this.getFiles();
const f = files[index];
files.splice(index, 1);
this.files$.next(files);
if (f) {
f.destroy();
}
}
public async removeClashingFiles() {
const fileClashes = this.uploadStatus$.getValue().fileClashes;
const filesToDestroy: FileWrapper[] = [];
const files = this.getFiles();
const newFiles = files.filter((file, i) => {
if (fileClashes[i].clash) {
filesToDestroy.push(files[i]);
return false;
}
return true;
});
this.files$.next(newFiles);
filesToDestroy.forEach((file) => {
file.destroy();
});
}
public getFiles() {
return this.files$.getValue();
}
private getFormatClashes(): {
formatsOk: boolean;
fileClashes: FileClash[];
} {
const files = this.getFiles();
const fileClashes = getFormatClashes(files);
const formatsOk = fileClashes.every((file) => file.clash === false);
if (formatsOk) {
this.commonFileFormat = formatsOk ? files[0].getStatus().results!.format : null;
}
return {
formatsOk,
fileClashes,
};
}
private createMergedMappings() {
const files = this.getFiles();
return createMergedMappings(files);
}
private getPipelines(): IngestPipeline[] {
const files = this.getFiles();
return files.map((file) => file.getPipeline());
}
public async import(
indexName: string,
createDataView: boolean = true
): Promise<FileUploadResults | null> {
if (this.mappings === null || this.pipelines === null || this.commonFileFormat === null) {
this.setStatus({
overallImportStatus: STATUS.FAILED,
});
return null;
}
this.setStatus({
overallImportStatus: STATUS.STARTED,
});
this.importer = await this.fileUpload.importerFactory(this.commonFileFormat, {});
this.inferenceId = getInferenceId(this.mappings);
if (this.inferenceId !== null) {
this.setStatus({
modelDeployed: STATUS.NOT_STARTED,
});
this.setStatus({
modelDeployed: STATUS.STARTED,
});
await this.autoDeploy();
this.setStatus({
modelDeployed: STATUS.COMPLETED,
});
}
this.setStatus({
indexCreated: STATUS.STARTED,
pipelineCreated: STATUS.STARTED,
});
let indexCreated = false;
let pipelineCreated = false;
let initializeImportResp: ImportResponse | undefined;
try {
initializeImportResp = await this.importer.initializeImport(
indexName,
this.settings,
this.mappings,
this.pipelines[0],
this.pipelines
);
this.timeFieldName = this.importer.getTimeField();
indexCreated = initializeImportResp.index !== undefined;
pipelineCreated = initializeImportResp.pipelineId !== undefined;
this.setStatus({
indexCreated: indexCreated ? STATUS.COMPLETED : STATUS.FAILED,
pipelineCreated: pipelineCreated ? STATUS.COMPLETED : STATUS.FAILED,
});
if (initializeImportResp.error) {
throw initializeImportResp.error;
}
} catch (e) {
this.setStatus({
overallImportStatus: STATUS.FAILED,
errors: [
{
title: i18n.translate('xpack.dataVisualizer.file.fileManager.errorInitializing', {
defaultMessage: 'Error initializing index and ingest pipeline',
}),
error: e,
},
],
});
return null;
}
if (!indexCreated || !pipelineCreated || !initializeImportResp) {
return null;
}
this.setStatus({
fileImport: STATUS.STARTED,
});
// import data
const files = this.getFiles();
try {
await Promise.all(
files.map(async (file, i) => {
await file.import(
initializeImportResp!.id,
indexName,
this.mappings!,
`${indexName}-${i}-pipeline`
);
})
);
} catch (error) {
this.setStatus({
overallImportStatus: STATUS.FAILED,
errors: [
{
title: i18n.translate('xpack.dataVisualizer.file.fileManager.errorImportingData', {
defaultMessage: 'Error importing data',
}),
error,
},
],
});
return null;
}
this.setStatus({
fileImport: STATUS.COMPLETED,
});
if (this.removePipelinesAfterImport) {
try {
this.setStatus({
pipelinesDeleted: STATUS.STARTED,
});
await this.importer.deletePipelines(
this.pipelines.map((p, i) => `${indexName}-${i}-pipeline`)
);
this.setStatus({
pipelinesDeleted: STATUS.COMPLETED,
});
} catch (error) {
this.setStatus({
pipelinesDeleted: STATUS.FAILED,
errors: [
{
title: i18n.translate(
'xpack.dataVisualizer.file.fileManager.errorDeletingPipelines',
{
defaultMessage: 'Error deleting pipelines',
}
),
error,
},
],
});
}
}
const dataView = '';
let dataViewResp;
if (createDataView) {
this.setStatus({
dataViewCreated: STATUS.STARTED,
});
const dataViewName = dataView === '' ? indexName : dataView;
dataViewResp = await createKibanaDataView(
dataViewName,
this.dataViewsContract,
this.timeFieldName ?? undefined
);
if (dataViewResp.success === false) {
this.setStatus({
overallImportStatus: STATUS.FAILED,
errors: [
{
title: i18n.translate('xpack.dataVisualizer.file.fileManager.errorCreatingDataView', {
defaultMessage: 'Error creating data view',
}),
error: dataViewResp.error,
},
],
});
return null;
} else {
this.setStatus({
dataViewCreated: STATUS.COMPLETED,
});
}
}
this.setStatus({
overallImportStatus: STATUS.COMPLETED,
});
return {
index: indexName,
dataView: dataViewResp ? { id: dataViewResp.id!, title: dataView! } : undefined,
inferenceId: this.inferenceId ?? undefined,
files: this.getFiles().map((file) => {
const status = file.getStatus();
return {
fileName: status.fileName,
docCount: status.docCount,
fileFormat: status.results!.format,
documentType: status.results!.document_type!,
};
}),
};
}
private async autoDeploy() {
if (this.inferenceId === null) {
return;
}
try {
const autoDeploy = new AutoDeploy(this.http, this.inferenceId);
await autoDeploy.deploy();
} catch (error) {
this.setStatus({
modelDeployed: STATUS.FAILED,
errors: [
{
title: i18n.translate('xpack.dataVisualizer.file.fileManager.errorDeployingModel', {
defaultMessage: 'Error deploying model',
}),
error,
},
],
});
}
}
private isTikaFormat() {
return this.commonFileFormat === 'tika';
}
private addSemanticTextField() {
if (
this.isTikaFormat() &&
this.autoAddSemanticTextField &&
this.autoAddInferenceEndpointName !== null &&
this.pipelines !== null &&
this.mappings !== null
) {
this.mappings.properties!.content = {
type: 'semantic_text',
inference_id: this.autoAddInferenceEndpointName,
};
this.pipelines.forEach((pipeline) => {
pipeline.processors.push({
set: {
field: 'content',
copy_from: 'attachment.content',
},
});
});
}
}
}

View file

@ -0,0 +1,213 @@
/*
* 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 { BehaviorSubject } from 'rxjs';
import type { FileUploadStartApi } from '@kbn/file-upload-plugin/public/api';
import type {
FindFileStructureResponse,
IngestPipeline,
} from '@kbn/file-upload-plugin/common/types';
import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
import { isSupportedFormat } from '../../../common/constants';
import { isTikaType } from '../../../common/utils/tika_utils';
import { processResults, readFile } from '../../application/common/components/utils';
import { analyzeTikaFile } from '../../application/file_data_visualizer/components/file_data_visualizer_view/tika_analyzer';
import { STATUS } from './file_manager';
import { FileSizeChecker } from '../../application/file_data_visualizer/components/file_data_visualizer_view/file_size_check';
interface AnalysisResults {
analysisStatus: STATUS;
fileContents: string;
results: FindFileStructureResponse | null;
explanation: string[] | undefined;
serverSettings: ReturnType<typeof processResults> | null;
analysisError?: any;
}
export type FileAnalysis = AnalysisResults & {
loaded: boolean;
importStatus: STATUS;
fileName: string;
fileContents: string;
fileSize: string;
data: ArrayBuffer | null;
fileTooLarge: boolean;
fileCouldNotBeRead: boolean;
fileCouldNotBeReadPermissionError: any;
serverError: any;
results: FindFileStructureResponse | null;
explanation: string[] | undefined;
importProgress: number;
docCount: number;
supportedFormat: boolean;
};
export class FileWrapper {
private analyzedFile$ = new BehaviorSubject<FileAnalysis>({
analysisStatus: STATUS.NOT_STARTED,
fileContents: '',
fileSize: '',
results: null,
explanation: undefined,
serverSettings: null,
loaded: false,
importStatus: STATUS.NOT_STARTED,
fileName: '',
data: null,
fileTooLarge: false,
fileCouldNotBeRead: false,
fileCouldNotBeReadPermissionError: false,
serverError: false,
importProgress: 0,
docCount: 0,
supportedFormat: true,
});
public readonly fileStatus$ = this.analyzedFile$.asObservable();
private fileSizeChecker: FileSizeChecker;
constructor(private file: File, private fileUpload: FileUploadStartApi) {
this.fileSizeChecker = new FileSizeChecker(fileUpload, file);
this.analyzedFile$.next({
...this.analyzedFile$.getValue(),
fileName: this.file.name,
loaded: false,
fileTooLarge: !this.fileSizeChecker.check(),
fileSize: this.fileSizeChecker.fileSizeFormatted(),
});
}
public destroy() {
this.analyzedFile$.complete();
}
public async analyzeFile() {
this.setStatus({ analysisStatus: STATUS.STARTED });
readFile(this.file).then(async ({ data, fileContents }) => {
// return after file has been read
// analysis will be done in the background
let analysisResults: AnalysisResults;
if (isTikaType(this.file.type)) {
analysisResults = await this.analyzeTika(data);
} else {
analysisResults = await this.analyzeStandardFile(fileContents, {});
}
const supportedFormat = isSupportedFormat(analysisResults.results?.format ?? '');
this.setStatus({
...analysisResults,
loaded: true,
fileName: this.file.name,
fileContents,
data,
supportedFormat,
});
});
}
private async analyzeTika(data: ArrayBuffer, isRetry = false): Promise<AnalysisResults> {
const { tikaResults, standardResults } = await analyzeTikaFile(data, this.fileUpload);
const serverSettings = processResults(standardResults);
return {
fileContents: tikaResults.content,
results: standardResults.results,
explanation: standardResults.results.explanation,
serverSettings,
analysisStatus: STATUS.COMPLETED,
};
}
private async analyzeStandardFile(
fileContents: string,
overrides: Record<string, string>,
isRetry = false
): Promise<AnalysisResults> {
try {
const resp = await this.fileUpload.analyzeFile(fileContents, overrides);
const serverSettings = processResults(resp);
return {
fileContents,
results: resp.results,
explanation: resp.results.explanation,
serverSettings,
analysisStatus: STATUS.COMPLETED,
};
} catch (e) {
return {
fileContents,
results: null,
explanation: undefined,
serverSettings: null,
analysisError: e,
analysisStatus: STATUS.FAILED,
};
}
}
private setStatus(status: Partial<FileAnalysis>) {
this.analyzedFile$.next({
...this.getStatus(),
...status,
});
}
public getStatus() {
return this.analyzedFile$.getValue();
}
public getFileName() {
return this.analyzedFile$.getValue().fileName;
}
public getMappings() {
return this.analyzedFile$.getValue().results?.mappings;
}
public getPipeline(): IngestPipeline {
return (
this.analyzedFile$.getValue().results?.ingest_pipeline ?? {
description: '',
processors: [],
}
);
}
public getFormat() {
return this.analyzedFile$.getValue().results?.format;
}
public getData() {
return this.analyzedFile$.getValue().data;
}
public async import(id: string, index: string, mappings: MappingTypeMapping, pipelineId: string) {
this.setStatus({ importStatus: STATUS.STARTED });
const format = this.analyzedFile$.getValue().results!.format;
const importer = await this.fileUpload.importerFactory(format, {
excludeLinesPattern: this.analyzedFile$.getValue().results!.exclude_lines_pattern,
multilineStartPattern: this.analyzedFile$.getValue().results!.multiline_start_pattern,
});
importer.initializeWithoutCreate(index, mappings, this.getPipeline());
const data = this.getData();
if (data === null) {
this.setStatus({ importStatus: STATUS.FAILED });
return;
}
importer.read(data);
try {
const resp = await importer.import(id, index, pipelineId, (p) => {
this.setStatus({ importProgress: p });
});
this.setStatus({ docCount: resp.docCount, importStatus: STATUS.COMPLETED });
return resp;
} catch (error) {
this.setStatus({ importStatus: STATUS.FAILED });
return;
}
}
}

View file

@ -0,0 +1,8 @@
/*
* 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 { FileManager } from './file_manager';

View file

@ -0,0 +1,196 @@
/*
* 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 { FileAnalysis, FileWrapper } from './file_wrapper';
export enum CLASH_TYPE {
MAPPING,
FORMAT,
UNSUPPORTED,
}
export interface MappingClash {
fieldName: string;
existingType: string;
clashingType: { fileName: string; newType: string; fileIndex: number };
}
export interface FileClash {
fileName: string;
clash: boolean;
clashType?: CLASH_TYPE;
}
export function createMergedMappings(files: FileWrapper[]) {
const mappings = files.map((file) => file.getMappings() ?? { properties: {} });
// stringify each mappings and see if they are the same, if so return the first one.
// otherwise drill down and extract each field with it's type.
const mappingsString = mappings.map((m) => JSON.stringify(m));
if (mappingsString.every((m) => m === mappingsString[0])) {
return { mergedMappings: mappings[0], mappingClashes: [] };
}
const fieldsPerFile = mappings.map((m) => {
if (m.properties === undefined) {
return [];
}
return Object.entries(m.properties)
.map(([key, value]) => {
return { name: key, value };
})
.sort((a, b) => a.name.localeCompare(b.name));
});
const mappingClashes: MappingClash[] = [];
const mergedMappingsMap = fieldsPerFile.reduce((acc, fields, i) => {
fields.forEach((field) => {
if (!acc.has(field.name)) {
acc.set(field.name, field.value);
} else {
const existingField = acc.get(field.name);
if (existingField.type !== field.value.type) {
// if either new or existing field is text or keyword, we should allow the clash
// and replace the existing field with the new field if the existing is keyword =
if (existingField.type === 'keyword' && field.value.type === 'text') {
// the existing field is keyword and the new field is text, replace the existing field with the text version
acc.set(field.name, field.value);
} else if (existingField.type === 'text' && field.value.type === 'keyword') {
// do nothing
} else {
mappingClashes.push({
fieldName: field.name,
existingType: existingField.type,
clashingType: {
fileName: files[i].getFileName(),
newType: field.value.type,
fileIndex: i,
},
});
}
}
}
});
return acc;
}, new Map<string, any>());
const mergedMappings = {
properties: Object.fromEntries(mergedMappingsMap),
};
return { mergedMappings, mappingClashes };
}
export function getMappingClashInfo(
mappingClashes: MappingClash[],
filesStatus: FileAnalysis[]
): FileClash[] {
const clashCounts = filesStatus
.reduce<Array<{ index: number; count: number }>>((acc, file, i) => {
const ff = { index: i, count: 0 };
mappingClashes.forEach((clash) => {
if (clash.clashingType.fileIndex === i) {
ff.count++;
}
});
acc.push(ff);
return acc;
}, [])
.sort((a, b) => b.count - a.count);
const middleIndex = Math.floor(clashCounts.length / 2);
const median = clashCounts[middleIndex].count;
const medianAboveZero = median > 0;
const zeroCount = clashCounts.filter((c) => c.count === 0).length;
const aboveZeroCount = clashCounts.length - zeroCount;
const allClash = zeroCount === aboveZeroCount;
clashCounts.sort((a, b) => a.index - b.index);
return clashCounts.map((c) => {
return {
fileName: filesStatus[c.index].fileName,
clash:
allClash ||
(medianAboveZero === false && c.count > 0) ||
(medianAboveZero && c.count === 0),
clashType: CLASH_TYPE.MAPPING,
};
});
}
export function getFormatClashes(files: FileWrapper[]): FileClash[] {
const formatMap = files
.map((file) => file.getFormat())
.reduce((acc, format, i) => {
if (format === undefined) {
acc.set('unknown', (acc.get('unknown') ?? 0) + 1);
} else {
acc.set(format, (acc.get(format) ?? 0) + 1);
}
return acc;
}, new Map<string, number>());
// return early if there is only one format and it is supported
if (formatMap.size === 1 && formatMap.has('unknown') === false) {
return files.map((f) => ({
fileName: f.getFileName(),
clash: false,
}));
}
const formatArray = Array.from(formatMap.entries()).sort((a, b) => b[1] - a[1]);
const fileClashes = files.map((f) => {
return {
fileName: f.getFileName(),
clash: f.getStatus().supportedFormat === false,
clashType: CLASH_TYPE.UNSUPPORTED,
};
});
const topCount = formatArray[0];
// if the top count is for an unsupported format,
// return the fileClashes containing unsupported formats
if (topCount[0] === 'unknown') {
return fileClashes;
}
// Check if all counts are the same,
// mark all files as clashing
const counts = Array.from(formatMap.values());
const allCountsSame = counts.every((count) => count === counts[0]);
if (allCountsSame) {
return files.map((f) => {
return {
fileName: f.getFileName(),
clash: true,
clashType: f.getStatus().supportedFormat ? CLASH_TYPE.FORMAT : CLASH_TYPE.UNSUPPORTED,
};
});
}
return files.map((f) => {
let clashType: CLASH_TYPE | undefined;
if (f.getStatus().supportedFormat === false) {
clashType = CLASH_TYPE.UNSUPPORTED;
} else if (f.getFormat() !== topCount[0]) {
clashType = CLASH_TYPE.FORMAT;
}
return {
fileName: f.getFileName(),
clash: clashType !== undefined,
clashType,
};
});
}

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { EuiFilePickerProps } from '@elastic/eui';
import { EuiFormRow, EuiFilePicker, EuiSpacer } from '@elastic/eui';
import type { EuiFilePickerClass } from '@elastic/eui/src/components/form/file_picker/file_picker';
import { i18n } from '@kbn/i18n';
import type { FC } from 'react';
import React, { useCallback, useRef } from 'react';
import type { FileManager } from './file_manager';
interface Props {
fileManager: FileManager;
}
export const FilePicker: FC<Props> = ({ fileManager: fm }) => {
const filePickerRef = useRef<EuiFilePickerClass>(null);
const onFilePickerChange = useCallback(
async (files: FileList | null) => {
if (files && files.length > 0) {
await fm.addFiles(files);
// Clear the file picker after adding files
filePickerRef.current?.removeFiles();
}
},
[fm]
);
return (
<>
<EuiFormRow
fullWidth
helpText={i18n.translate(
'xpack.dataVisualizer.file.aboutPanel.supportedFormatsDescription',
{
defaultMessage: 'Supported formats: PDF, TXT, CSV, log files and NDJSON',
}
)}
>
<EuiFilePicker
ref={filePickerRef as React.Ref<Omit<EuiFilePickerProps, 'stylesMemoizer'>>}
id="filePicker"
fullWidth
display="large"
compressed
multiple
initialPromptText={i18n.translate(
'xpack.dataVisualizer.file.filePicker.selectOrDragAndDropFiles',
{
defaultMessage: 'Select or drag and drop files',
}
)}
onChange={(files) => onFilePickerChange(files)}
/>
</EuiFormRow>
<EuiSpacer />
</>
);
};

View file

@ -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 { FC } from 'react';
import React from 'react';
import {
EuiPanel,
EuiSpacer,
EuiText,
EuiFlexGroup,
EuiFlexItem,
EuiButtonIcon,
EuiProgress,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { FileAnalysis } from './file_manager/file_wrapper';
import { STATUS, type UploadStatus } from './file_manager/file_manager';
import { CLASH_TYPE } from './file_manager/merge_tools';
interface Props {
uploadStatus: UploadStatus;
fileStatus: FileAnalysis;
deleteFile: () => void;
index: number;
}
export const FileStatus: FC<Props> = ({ fileStatus, uploadStatus, deleteFile, index }) => {
const fileClash = uploadStatus.fileClashes[index] ?? {
clash: false,
};
const importStarted =
uploadStatus.overallImportStatus === STATUS.STARTED ||
uploadStatus.overallImportStatus === STATUS.COMPLETED;
return (
<>
<EuiPanel
hasShadow={false}
hasBorder
paddingSize="s"
color={fileClash.clash ? 'danger' : 'transparent'}
>
{importStarted ? (
<>
<EuiProgress
value={Math.floor(fileStatus.importProgress)}
max={100}
size="s"
label={fileStatus.fileName}
valueText={true}
color={fileStatus.importProgress === 100 ? 'success' : 'primary'}
/>
</>
) : (
<>
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={8}>
<EuiText size="xs">
<span css={{ fontWeight: 'bold' }}>{fileStatus.fileName}</span>{' '}
<span>{fileStatus.fileSize}</span>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={true} />
<EuiFlexItem grow={false}>
<EuiButtonIcon
onClick={deleteFile}
iconType="trash"
size="xs"
color="danger"
aria-label={i18n.translate(
'xpack.dataVisualizer.file.fileStatus.deleteFile',
{
defaultMessage: 'Remove file',
}
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
{fileClash.clash ? (
<>
{fileClash.clashType === CLASH_TYPE.FORMAT ? (
<>
<EuiSpacer size="s" />
<EuiText size="xs" color="danger">
<FormattedMessage
id="xpack.dataVisualizer.file.fileStatus.fileFormatClash"
defaultMessage="File format different from other files"
/>
</EuiText>
</>
) : null}
{fileClash.clashType === CLASH_TYPE.MAPPING ? (
<>
<EuiSpacer size="s" />
<EuiText size="xs" color="danger">
<FormattedMessage
id="xpack.dataVisualizer.file.fileStatus.mappingClash"
defaultMessage="Mappings incompatible with other files"
/>
</EuiText>
</>
) : null}
{fileClash.clashType === CLASH_TYPE.UNSUPPORTED ? (
<>
<EuiSpacer size="s" />
<EuiText size="xs" color="danger">
<FormattedMessage
id="xpack.dataVisualizer.file.fileStatus.fileFormatNotSupported"
defaultMessage="File format not supported"
/>
</EuiText>
</>
) : null}
</>
) : null}
</>
)}
</EuiPanel>
<EuiSpacer size="s" />
</>
);
};

View file

@ -0,0 +1,74 @@
/*
* 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 { FC, PropsWithChildren } from 'react';
import React from 'react';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { IndicesIndexSettings } from '@elastic/elasticsearch/lib/api/types';
import type { FileUploadResults } from '@kbn/file-upload-common';
import type { ResultLinks } from '../../common/app';
import type { GetAdditionalLinks } from '../application/common/components/results_links';
import { getCoreStart, getPluginsStart } from '../kibana_services';
import { FileUploadLiteView } from './file_upload_lite_view';
export interface Props {
resultLinks?: ResultLinks;
getAdditionalLinks?: GetAdditionalLinks;
setUploadResults?: (results: FileUploadResults) => void;
autoAddInference?: string;
indexSettings?: IndicesIndexSettings;
onClose?: () => void;
}
export const FileDataVisualizerLite: FC<Props> = ({
getAdditionalLinks,
resultLinks,
setUploadResults,
autoAddInference,
indexSettings,
onClose,
}) => {
const coreStart = getCoreStart();
const { data, maps, embeddable, share, fileUpload, cloud, fieldFormats } = getPluginsStart();
const services = {
...coreStart,
data,
maps,
embeddable,
share,
fileUpload,
fieldFormats,
};
const EmptyContext: FC<PropsWithChildren<unknown>> = ({ children }) => <>{children}</>;
const CloudContext = cloud?.CloudContextProvider ?? EmptyContext;
return (
<KibanaRenderContextProvider {...coreStart}>
<KibanaContextProvider services={{ ...services }}>
<CloudContext>
<FileUploadLiteView
dataStart={data}
http={coreStart.http}
fileUpload={fileUpload}
getAdditionalLinks={getAdditionalLinks}
resultLinks={resultLinks}
capabilities={coreStart.application.capabilities}
setUploadResults={setUploadResults}
autoAddInference={autoAddInference}
indexSettings={indexSettings}
onClose={onClose}
/>
</CloudContext>
</KibanaContextProvider>
</KibanaRenderContextProvider>
);
};
// exporting as default so it can be used with React.lazy
// eslint-disable-next-line import/no-default-export
export default FileDataVisualizerLite;

View file

@ -0,0 +1,65 @@
/*
* 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 { i18n } from '@kbn/i18n';
import type { Trigger, UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import {
OPEN_FILE_UPLOAD_LITE_ACTION,
OPEN_FILE_UPLOAD_LITE_TRIGGER,
} from '@kbn/file-upload-common';
import type { OpenFileUploadLiteContext } from '@kbn/file-upload-common';
import type { DataVisualizerStartDependencies } from '../application/common/types/data_visualizer_plugin';
import { createFlyout } from './flyout/create_flyout';
export const createOpenFileUploadLiteTrigger: Trigger = {
id: OPEN_FILE_UPLOAD_LITE_TRIGGER,
title: i18n.translate('xpack.dataVisualizer.file.lite.actions.triggerTitle', {
defaultMessage: 'Open file upload UI',
}),
description: i18n.translate('xpack.dataVisualizer.file.lite.actions.triggerDescription', {
defaultMessage: 'Open file upload UI',
}),
};
export function createOpenFileUploadLiteAction(
coreStart: CoreStart,
plugins: DataVisualizerStartDependencies
): UiActionsActionDefinition<OpenFileUploadLiteContext> {
return {
id: 'create-open-file-upload-lite-action',
type: OPEN_FILE_UPLOAD_LITE_ACTION,
getIconType(context): string {
return 'machineLearningApp';
},
getDisplayName: () =>
i18n.translate('xpack.dataVisualizer.file.lite.actions.displayName', {
defaultMessage: 'Open file upload UI',
}),
async execute({
onUploadComplete,
autoAddInference,
indexSettings,
}: OpenFileUploadLiteContext) {
try {
const { share, data } = plugins;
createFlyout(coreStart, share, data, {
onUploadComplete,
autoAddInference,
indexSettings,
});
} catch (e) {
return Promise.reject();
}
},
async isCompatible() {
return true;
},
};
}

View file

@ -0,0 +1,239 @@
/*
* 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 {
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiLoadingSpinner,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import type { ApplicationStart, HttpSetup } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { FileUploadStartApi } from '@kbn/file-upload-plugin/public/api';
import { FormattedMessage } from '@kbn/i18n-react';
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import useObservable from 'react-use/lib/useObservable';
import type { IndicesIndexSettings } from '@elastic/elasticsearch/lib/api/types';
import type { FileUploadResults } from '@kbn/file-upload-common';
import type { ResultLinks } from '../../common/app';
import type { GetAdditionalLinks } from '../application/common/components/results_links';
import { FileClashWarning } from './file_clash_warning';
import { FileManager } from './file_manager';
import { STATUS } from './file_manager/file_manager';
import { FilePicker } from './file_picker';
import { FileStatus } from './file_status';
import { IndexInput } from './index_input';
import { OverallUploadStatus } from './overall_upload_status';
import { ImportErrors } from './import_errors';
import { DataViewIllustration } from './data_view_illustration';
interface Props {
dataStart: DataPublicPluginStart;
http: HttpSetup;
fileUpload: FileUploadStartApi;
resultLinks?: ResultLinks;
capabilities: ApplicationStart['capabilities'];
getAdditionalLinks?: GetAdditionalLinks;
setUploadResults?: (results: FileUploadResults) => void;
autoAddInference?: string;
indexSettings?: IndicesIndexSettings;
onClose?: () => void;
}
export const FileUploadLiteView: FC<Props> = ({
fileUpload,
http,
dataStart,
setUploadResults,
autoAddInference,
indexSettings,
onClose,
}) => {
const [indexName, setIndexName] = useState<string>('');
const [indexValidationStatus, setIndexValidationStatus] = useState<STATUS>(STATUS.NOT_STARTED);
const fm = useMemo(
() =>
new FileManager(
fileUpload,
http,
dataStart.dataViews,
autoAddInference ?? null,
true,
indexSettings
),
[autoAddInference, dataStart.dataViews, fileUpload, http, indexSettings]
);
const deleteFile = useCallback((i: number) => fm.removeFile(i), [fm]);
const filesStatus = useObservable(fm.fileAnalysisStatus$, []);
const uploadStatus = useObservable(fm.uploadStatus$, fm.uploadStatus$.getValue());
const fileClashes = useMemo(
() => uploadStatus.fileClashes.some((f) => f.clash),
[uploadStatus.fileClashes]
);
useEffect(() => {
return () => {
fm.destroy();
};
}, [fm]);
const uploadInProgress =
uploadStatus.overallImportStatus === STATUS.STARTED ||
uploadStatus.overallImportStatus === STATUS.COMPLETED ||
uploadStatus.overallImportStatus === STATUS.FAILED;
const onImportClick = useCallback(() => {
fm.import(indexName).then((res) => {
if (setUploadResults && res) {
setUploadResults(res);
}
});
}, [fm, indexName, setUploadResults]);
return (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.dataVisualizer.file.uploadView.uploadFileTitle"
defaultMessage="Upload a file"
/>
</h3>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<>
<>
{uploadStatus.overallImportStatus === STATUS.NOT_STARTED ? (
<>
<EuiText>
<p>
<FormattedMessage
id="xpack.dataVisualizer.file.uploadView.uploadFileDescription"
defaultMessage="Upload your file, analyze its data, and import the data into an Elasticsearch index. The data can also be automatically vectorized using semantic text."
/>
</p>
</EuiText>
<EuiSpacer />
<FilePicker fileManager={fm} />
</>
) : null}
{uploadStatus.overallImportStatus === STATUS.NOT_STARTED ? (
<>
{filesStatus.map((status, i) => (
<FileStatus
uploadStatus={uploadStatus}
fileStatus={status}
key={i}
deleteFile={() => deleteFile(i)}
index={i}
/>
))}
{fileClashes ? (
<FileClashWarning
uploadStatus={uploadStatus}
filesStatus={filesStatus}
removeClashingFiles={() => fm.removeClashingFiles()}
/>
) : null}
<EuiSpacer />
</>
) : null}
{uploadStatus.overallImportStatus === STATUS.NOT_STARTED &&
filesStatus.length > 0 &&
uploadStatus.analysisOk ? (
<>
<IndexInput
setIndexName={setIndexName}
setIndexValidationStatus={setIndexValidationStatus}
fileUpload={fileUpload}
/>
</>
) : null}
{uploadInProgress ? (
<>
<EuiFlexGroup>
<EuiFlexItem />
<EuiFlexItem grow={false}>
<DataViewIllustration />
</EuiFlexItem>
<EuiFlexItem />
</EuiFlexGroup>
<EuiSpacer size="xl" />
<OverallUploadStatus uploadStatus={uploadStatus} filesStatus={filesStatus} />
{uploadStatus.overallImportStatus === STATUS.FAILED ? (
<ImportErrors uploadStatus={uploadStatus} />
) : null}
</>
) : null}
</>
</>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={onClose} flush="left">
<FormattedMessage
id="xpack.dataVisualizer.file.uploadView.closeButton"
defaultMessage="Close"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={true} />
<EuiFlexItem grow={false}>
{uploadStatus.overallImportStatus === STATUS.STARTED ? (
<EuiFlexGroup gutterSize="none" alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem>
<EuiButtonEmpty onClick={onClose} disabled={true}>
<FormattedMessage
id="xpack.dataVisualizer.file.uploadView.importingButton"
defaultMessage="Importing"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
) : null}
{uploadStatus.overallImportStatus === STATUS.NOT_STARTED ? (
<EuiButton
disabled={indexName === '' || indexValidationStatus !== STATUS.COMPLETED}
onClick={onImportClick}
>
<FormattedMessage
id="xpack.dataVisualizer.file.uploadView.importButton"
defaultMessage="Import"
/>
</EuiButton>
) : null}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</>
);
};

View file

@ -0,0 +1,53 @@
/*
* 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 { FC } from 'react';
import React from 'react';
import type { IndicesIndexSettings } from '@elastic/elasticsearch/lib/api/types';
import type { FileUploadResults } from '@kbn/file-upload-common';
import type { ResultLinks } from '../../../common/app';
const FileDataVisualizerLiteComponent = React.lazy(() => import('../file_upload_lite'));
export const FileDataVisualizerLiteWrapper: FC<{
resultLinks?: ResultLinks;
setUploadResults?: (results: FileUploadResults) => void;
autoAddInference?: string;
indexSettings?: IndicesIndexSettings;
onClose?: () => void;
}> = ({ resultLinks, setUploadResults, autoAddInference, indexSettings, onClose }) => {
return (
<React.Suspense fallback={<div />}>
<FileDataVisualizerLiteComponent
resultLinks={resultLinks}
setUploadResults={setUploadResults}
autoAddInference={autoAddInference}
indexSettings={indexSettings}
onClose={onClose}
/>
</React.Suspense>
);
};
export function getFileDataVisualizerLiteWrapper(
resultLinks?: ResultLinks,
setUploadResults?: (results: FileUploadResults) => void,
autoAddInference?: string,
indexSettings?: IndicesIndexSettings,
onClose?: () => void
) {
return (
<FileDataVisualizerLiteWrapper
resultLinks={resultLinks}
setUploadResults={setUploadResults}
autoAddInference={autoAddInference}
indexSettings={indexSettings}
onClose={onClose}
/>
);
}

View file

@ -0,0 +1,99 @@
/*
* 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 { FC } from 'react';
import React, { Suspense, lazy } from 'react';
import { takeUntil, distinctUntilChanged, skip } from 'rxjs';
import { from } from 'rxjs';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import type { FileUploadResults, OpenFileUploadLiteContext } from '@kbn/file-upload-common';
import { EuiFlyoutHeader, EuiSkeletonText, EuiSpacer, EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
export function createFlyout(
coreStart: CoreStart,
share: SharePluginStart,
data: DataPublicPluginStart,
props: OpenFileUploadLiteContext
) {
const {
http,
overlays,
application: { currentAppId$ },
...startServices
} = coreStart;
const LazyFlyoutContents = lazy(async () => {
const { FlyoutContents } = await import('./flyout_contents');
return {
default: FlyoutContents,
};
});
let results: FileUploadResults | null = null;
const { onUploadComplete, autoAddInference, indexSettings } = props;
const onFlyoutClose = () => {
flyoutSession.close();
if (results !== null && typeof onUploadComplete === 'function') {
onUploadComplete(results);
}
};
const flyoutSession = overlays.openFlyout(
toMountPoint(
<Suspense fallback={<LoadingContents />}>
<LazyFlyoutContents
coreStart={coreStart}
share={share}
data={data}
props={{ autoAddInference, indexSettings }}
onFlyoutClose={onFlyoutClose}
setUploadResults={(res) => {
if (res) {
results = res;
}
}}
/>
</Suspense>,
startServices
),
{
'data-test-subj': 'mlFlyoutLayerSelector',
ownFocus: true,
onClose: onFlyoutClose,
size: '500px',
}
);
// Close the flyout when user navigates out of the current plugin
currentAppId$
.pipe(skip(1), takeUntil(from(flyoutSession.onClose)), distinctUntilChanged())
.subscribe(() => {
flyoutSession.close();
});
}
const LoadingContents: FC = () => (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.dataVisualizer.file.uploadView.uploadFileTitle"
defaultMessage="Upload a file"
/>
</h3>
</EuiTitle>
</EuiFlyoutHeader>
<EuiSpacer />
<EuiSkeletonText />
</>
);

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FC } from 'react';
import React from 'react';
import type { IndicesIndexSettings } from '@elastic/elasticsearch/lib/api/types';
import type { FileUploadResults } from '@kbn/file-upload-common';
import { getFileDataVisualizerLiteWrapper } from './component_wrapper';
interface Props {
onClose?: () => void;
setUploadResults?: (results: FileUploadResults) => void;
autoAddInference?: string;
indexSettings?: IndicesIndexSettings;
}
export const FileUploadLiteFlyoutContents: FC<Props> = ({
onClose,
setUploadResults,
autoAddInference,
indexSettings,
}) => {
const Wrapper = getFileDataVisualizerLiteWrapper(
undefined,
setUploadResults,
autoAddInference,
indexSettings,
onClose
);
return <>{Wrapper}</>;
};

View file

@ -0,0 +1,52 @@
/*
* 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 React from 'react';
import type { FC } from 'react';
import type { CoreStart } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { FileUploadResults, OpenFileUploadLiteContext } from '@kbn/file-upload-common';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { FileUploadLiteFlyoutContents } from './flyout';
interface Props {
coreStart: CoreStart;
share: SharePluginStart;
data: DataPublicPluginStart;
props: OpenFileUploadLiteContext;
onFlyoutClose: () => void;
setUploadResults: (results: FileUploadResults) => void;
}
export const FlyoutContents: FC<Props> = ({
coreStart,
share,
data,
props: { autoAddInference, indexSettings },
onFlyoutClose,
setUploadResults,
}) => {
return (
<KibanaContextProvider
services={{
...coreStart,
share,
data,
}}
>
<FileUploadLiteFlyoutContents
autoAddInference={autoAddInference}
indexSettings={indexSettings}
onClose={() => {
onFlyoutClose();
}}
setUploadResults={setUploadResults}
/>
</KibanaContextProvider>
);
};

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import type { FC } from 'react';
import React from 'react';
import { type UploadStatus } from './file_manager/file_manager';
interface Props {
uploadStatus: UploadStatus;
}
export const ImportErrors: FC<Props> = ({ uploadStatus }) => {
return (
<>
<EuiSpacer />
{uploadStatus.errors.map((error, index) => (
<React.Fragment key={index}>
<EuiSpacer size="m" />
<EuiCallOut title={error.title} color="danger" iconType="alert">
<p>{JSON.stringify(error)}</p>
</EuiCallOut>
</React.Fragment>
))}
</>
);
};

View file

@ -0,0 +1,121 @@
/*
* 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 { EuiFieldText, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
import type { FC } from 'react';
import React, { useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import type { FileUploadStartApi } from '@kbn/file-upload-plugin/public/api';
import { i18n } from '@kbn/i18n';
import useMountedState from 'react-use/lib/useMountedState';
import { FormattedMessage } from '@kbn/i18n-react';
import { STATUS } from './file_manager/file_manager';
interface Props {
setIndexName: (name: string) => void;
setIndexValidationStatus: (status: STATUS) => void;
fileUpload: FileUploadStartApi;
}
export const IndexInput: FC<Props> = ({ setIndexName, setIndexValidationStatus, fileUpload }) => {
const [indexNameLocal, setIndexNameLocal] = useState('');
const [indexNameError, setIndexNameError] = useState('');
const isMounted = useMountedState();
useDebounce(
async () => {
setIndexValidationStatus(STATUS.STARTED);
if (indexNameLocal === '') {
setIndexValidationStatus(STATUS.COMPLETED);
setIndexNameError('');
setIndexName('');
return;
}
const exists = await fileUpload.checkIndexExists(indexNameLocal);
const error = exists
? i18n.translate(
'xpack.dataVisualizer.file.importView.indexNameAlreadyExistsErrorMessage',
{
defaultMessage: 'Index name already exists',
}
)
: isIndexNameValid(indexNameLocal);
if (!isMounted()) {
return;
}
setIndexName(indexNameLocal);
setIndexNameError(error);
setIndexValidationStatus(error === '' ? STATUS.COMPLETED : STATUS.FAILED);
},
250,
[indexNameLocal]
);
return (
<>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.dataVisualizer.file.importView.createIndexTitle"
defaultMessage="Create new index"
/>
</h3>
</EuiTitle>
<EuiSpacer size="xs" />
<EuiFormRow
label={i18n.translate('xpack.dataVisualizer.file.importView.indexNameLabel', {
defaultMessage: 'Index name',
})}
isInvalid={indexNameError !== ''}
error={indexNameError}
fullWidth
helpText={i18n.translate(
'xpack.dataVisualizer.file.importView.indexNameContainsIllegalCharactersErrorMessage',
{
defaultMessage:
'Index names must be lowercase and can only contain hyphens and numbers.',
}
)}
>
<EuiFieldText
fullWidth
value={indexNameLocal}
onChange={(e) => setIndexNameLocal(e.target.value)}
placeholder={i18n.translate(
'xpack.dataVisualizer.file.importView.indexNameContainsIllegalCharactersErrorMessage',
{
defaultMessage: 'Add name to index',
}
)}
/>
</EuiFormRow>
</>
);
};
function isIndexNameValid(name: string) {
const reg = new RegExp('[\\\\/*?"<>|\\s,#]+');
if (
name !== name.toLowerCase() || // name should be lowercase
name === '.' ||
name === '..' || // name can't be . or ..
name.match(/^[-_+]/) !== null || // name can't start with these chars
name.match(reg) !== null // name can't contain these chars
) {
return i18n.translate(
'xpack.dataVisualizer.file.importView.indexNameContainsIllegalCharactersErrorMessage',
{
defaultMessage: 'Index name contains illegal characters',
}
);
}
return '';
}

View file

@ -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 { EuiProgress } from '@elastic/eui';
import type { FC } from 'react';
import React from 'react';
import type { FileAnalysis } from './file_manager/file_wrapper';
interface Props {
filesStatus: FileAnalysis[];
}
export const OverallUploadProgress: FC<Props> = ({ filesStatus }) => {
const overallProgress =
filesStatus.map((file) => file.importProgress).reduce((acc, progress) => acc + progress, 0) /
filesStatus.length;
return <EuiProgress value={overallProgress} max={100} size="s" />;
};

View file

@ -0,0 +1,104 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FC } from 'react';
import React from 'react';
import type { EuiStepStatus } from '@elastic/eui';
import { EuiSpacer, EuiSteps } from '@elastic/eui';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { i18n } from '@kbn/i18n';
import type { FileAnalysis } from './file_manager/file_wrapper';
import { STATUS } from './file_manager/file_manager';
import { type UploadStatus } from './file_manager/file_manager';
import { FileStatus } from './file_status';
interface Props {
uploadStatus: UploadStatus;
filesStatus: FileAnalysis[];
}
export const OverallUploadStatus: FC<Props> = ({ filesStatus, uploadStatus }) => {
const generateStatus = (statuses: STATUS[]): EuiStepStatus => {
if (statuses.includes(STATUS.STARTED)) {
return 'current';
} else if (statuses.includes(STATUS.FAILED)) {
return 'danger';
} else if (statuses.every((status) => status === STATUS.COMPLETED)) {
return 'complete';
} else {
return 'incomplete';
}
};
const css = {
'.euiStep__content': { paddingBlockEnd: '0px' },
};
const steps: EuiContainedStepProps[] = [
...(uploadStatus.modelDeployed === STATUS.NA
? []
: [
{
title: i18n.translate('xpack.dataVisualizer.file.overallUploadStatus.deployingModel', {
defaultMessage: 'Deploying model',
}),
children: <></>,
status: generateStatus([uploadStatus.modelDeployed]),
},
]),
{
title: i18n.translate(
'xpack.dataVisualizer.file.overallUploadStatus.creatingIndexAndIngestPipeline',
{
defaultMessage: 'Creating index and ingest pipeline',
}
),
children: <></>,
status: generateStatus([uploadStatus.indexCreated, uploadStatus.pipelineCreated]),
},
{
title: i18n.translate('xpack.dataVisualizer.file.overallUploadStatus.uploadingFiles', {
defaultMessage: 'Uploading files',
}),
children: (
<>
{filesStatus.map((status, i) => (
<FileStatus
uploadStatus={uploadStatus}
fileStatus={status}
key={i}
deleteFile={() => {}}
index={i}
/>
))}
<EuiSpacer />
</>
),
status: generateStatus([uploadStatus.fileImport]),
},
{
title: i18n.translate('xpack.dataVisualizer.file.overallUploadStatus.creatingDataView', {
defaultMessage: 'Creating data view',
}),
children: <></>,
status: generateStatus([uploadStatus.dataViewCreated]),
},
{
title: i18n.translate('xpack.dataVisualizer.file.overallUploadStatus.uploadComplete', {
defaultMessage: 'Upload complete',
}),
children: <></>,
status: uploadStatus.overallImportStatus === STATUS.COMPLETED ? 'complete' : 'incomplete',
},
];
return (
<>
<EuiSteps steps={steps} titleSize="xxs" css={css} />
</>
);
};

View file

@ -21,6 +21,7 @@ import type {
DataVisualizerStartDependencies,
} from './application/common/types/data_visualizer_plugin';
import { registerEmbeddables } from './application/index_data_visualizer/embeddables/field_stats';
import { registerUiActions } from './register_ui_actions';
export type DataVisualizerPluginSetup = ReturnType<DataVisualizerPlugin['setup']>;
export type DataVisualizerPluginStart = ReturnType<DataVisualizerPlugin['start']>;
@ -66,6 +67,11 @@ export class DataVisualizerPlugin
public start(core: CoreStart, plugins: DataVisualizerStartDependencies) {
setStartServices(core, plugins);
if (plugins.uiActions) {
registerUiActions(core, plugins);
}
const {
getFileDataVisualizerComponent,
getIndexDataVisualizerComponent,

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreStart } from '@kbn/core/public';
import {
OPEN_FILE_UPLOAD_LITE_ACTION,
OPEN_FILE_UPLOAD_LITE_TRIGGER,
} from '@kbn/file-upload-common';
import {
createOpenFileUploadLiteAction,
createOpenFileUploadLiteTrigger,
} from './lite/file_upload_lite_action';
import type { DataVisualizerStartDependencies } from './application/common/types/data_visualizer_plugin';
export function registerUiActions(coreStart: CoreStart, plugins: DataVisualizerStartDependencies) {
const { uiActions } = plugins;
if (uiActions === undefined) {
return;
}
const categorizationADJobAction = createOpenFileUploadLiteAction(coreStart, plugins);
uiActions.registerTrigger(createOpenFileUploadLiteTrigger);
uiActions.addTriggerActionAsync(
OPEN_FILE_UPLOAD_LITE_TRIGGER,
OPEN_FILE_UPLOAD_LITE_ACTION,
async () => categorizationADJobAction
);
}

View file

@ -85,7 +85,8 @@
"@kbn/core-lifecycle-browser",
"@kbn/presentation-containers",
"@kbn/react-kibana-mount",
"@kbn/core-ui-settings-browser"
"@kbn/core-ui-settings-browser",
"@kbn/file-upload-common"
],
"exclude": [
"target/**/*",

View file

@ -8,11 +8,7 @@
import { fromByteArray } from 'base64-js';
import { lazyLoadModules } from '../lazy_load_bundle';
import type { IImporter, ImportFactoryOptions } from '../importer';
import type {
HasImportPermission,
FindFileStructureResponse,
PreviewTikaResponse,
} from '../../common/types';
import type { HasImportPermission, PreviewTikaResponse, AnalysisResult } from '../../common/types';
import type {
getMaxBytes,
getMaxBytesFormatted,
@ -63,10 +59,10 @@ interface HasImportPermissionParams {
export async function analyzeFile(
file: string,
params: Record<string, string> = {}
): Promise<FindFileStructureResponse> {
): Promise<AnalysisResult> {
const { getHttp } = await lazyLoadModules();
const body = JSON.stringify(file);
return await getHttp().fetch<FindFileStructureResponse>({
return await getHttp().fetch<AnalysisResult>({
path: `/internal/file_upload/analyze_file`,
method: 'POST',
version: '1',

View file

@ -9,6 +9,7 @@ import { chunk, intersection } from 'lodash';
import moment from 'moment';
import type {
IndicesIndexSettings,
IngestDeletePipelineResponse,
MappingTypeMapping,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { i18n } from '@kbn/i18n';
@ -85,9 +86,10 @@ export abstract class Importer implements IImporter {
index: string,
settings: IndicesIndexSettings,
mappings: MappingTypeMapping,
pipeline: IngestPipeline | undefined
pipeline: IngestPipeline | undefined,
createPipelines?: IngestPipeline[]
) {
let ingestPipeline: IngestPipelineWrapper | undefined;
let ingestPipelineWrapper: IngestPipelineWrapper | undefined;
if (pipeline !== undefined) {
updatePipelineTimezone(pipeline);
@ -98,12 +100,22 @@ export abstract class Importer implements IImporter {
}
// if no pipeline has been supplied,
// send an empty object
ingestPipeline = {
ingestPipelineWrapper = {
id: `${index}-pipeline`,
pipeline,
};
}
let createPipelinesWrappers: IngestPipelineWrapper[] | undefined;
if (createPipelines) {
createPipelinesWrappers = createPipelines.map((p, i) => {
return {
id: `${index}-${i}-pipeline`,
pipeline: p,
};
});
}
this._index = index;
this._pipeline = pipeline;
@ -123,10 +135,38 @@ export abstract class Importer implements IImporter {
data: [],
settings,
mappings,
ingestPipeline,
ingestPipeline: ingestPipelineWrapper,
createPipelines: createPipelinesWrappers,
});
}
public async initializeWithoutCreate(
index: string,
mappings: MappingTypeMapping,
pipeline: IngestPipeline | undefined
) {
if (pipeline !== undefined) {
if (pipelineContainsSpecialProcessors(pipeline)) {
// pipeline contains processors which we know are slow
// so reduce the chunk size significantly to avoid timeouts
this._chunkSize = REDUCED_CHUNK_SIZE;
}
}
this._index = index;
this._pipeline = pipeline;
// if an @timestamp field has been added to the
// mappings, use this field as the time field.
// This relies on the field being populated by
// the ingest pipeline on ingest
this._timeFieldName = isPopulatedObject(mappings.properties, [DEFAULT_TIME_FIELD])
? DEFAULT_TIME_FIELD
: undefined;
this._initialized = true;
}
public async import(
id: string,
index: string,
@ -250,6 +290,19 @@ export abstract class Importer implements IImporter {
body,
});
}
public async deletePipelines(pipelineIds: string[]) {
// remove_pipelines
// const body = JSON.stringify({
// pipelineIds,
// });
return await getHttp().fetch<IngestDeletePipelineResponse[]>({
path: `/internal/file_upload/remove_pipelines/${pipelineIds.join(',')}`,
method: 'DELETE',
version: '1',
});
}
}
function populateFailures(
@ -346,6 +399,7 @@ export function callImportRoute({
settings,
mappings,
ingestPipeline,
createPipelines,
}: {
id: string | undefined;
index: string;
@ -353,6 +407,7 @@ export function callImportRoute({
settings: IndicesIndexSettings;
mappings: MappingTypeMapping;
ingestPipeline: IngestPipelineWrapper | undefined;
createPipelines?: IngestPipelineWrapper[];
}) {
const query = id !== undefined ? { id } : {};
const body = JSON.stringify({
@ -361,6 +416,7 @@ export function callImportRoute({
settings,
mappings,
ingestPipeline,
createPipelines,
});
return getHttp().fetch<ImportResponse>({

View file

@ -7,6 +7,7 @@
import type {
IndicesIndexSettings,
IngestDeletePipelineResponse,
MappingTypeMapping,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
@ -43,8 +44,14 @@ export interface IImporter {
index: string,
settings: IndicesIndexSettings,
mappings: MappingTypeMapping,
pipeline: IngestPipeline | undefined
pipeline: IngestPipeline | undefined,
createPipelines?: IngestPipeline[]
): Promise<ImportResponse>;
initializeWithoutCreate(
index: string,
mappings: MappingTypeMapping,
pipeline: IngestPipeline | undefined
): void;
import(
id: string,
index: string,
@ -55,4 +62,5 @@ export interface IImporter {
getIndex(): string | undefined;
getTimeField(): string | undefined;
previewIndexTimeRange(): Promise<{ start: number | null; end: number | null }>;
deletePipelines(pipelineIds: string[]): Promise<IngestDeletePipelineResponse[]>;
}

View file

@ -22,6 +22,7 @@ export function importDataProvider({ asCurrentUser }: IScopedClusterClient) {
settings: IndicesIndexSettings,
mappings: MappingTypeMapping,
ingestPipeline: IngestPipelineWrapper | undefined,
createPipelines: IngestPipelineWrapper[],
data: InputData
): Promise<ImportResponse> {
let createdIndex;
@ -40,7 +41,14 @@ export function importDataProvider({ asCurrentUser }: IScopedClusterClient) {
createdIndex = index;
// create the pipeline if one has been supplied
if (pipelineId !== undefined) {
if (createPipelines !== undefined) {
for (const p of createPipelines) {
const resp = await createPipeline(p.id, p.pipeline);
if (resp.acknowledged !== true) {
throw resp;
}
}
} else if (pipelineId !== undefined) {
const resp = await createPipeline(pipelineId, pipeline);
if (resp.acknowledged !== true) {
throw resp;

View file

@ -38,10 +38,11 @@ function importData(
settings: IndicesIndexSettings,
mappings: MappingTypeMapping,
ingestPipeline: IngestPipelineWrapper,
createPipelines: IngestPipelineWrapper[],
data: InputData
) {
const { importData: importDataFunc } = importDataProvider(client);
return importDataFunc(id, index, settings, mappings, ingestPipeline, data);
return importDataFunc(id, index, settings, mappings, ingestPipeline, createPipelines, data);
}
/**
@ -183,7 +184,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup<StartDeps, unknown>, logge
async (context, request, response) => {
try {
const { id } = request.query;
const { index, data, settings, mappings, ingestPipeline } = request.body;
const { index, data, settings, mappings, ingestPipeline, createPipelines } = request.body;
const esClient = (await context.core).elasticsearch.client;
// `id` being `undefined` tells us that this is a new import due to create a new index.
@ -201,6 +202,7 @@ export function fileUploadRoutes(coreSetup: CoreSetup<StartDeps, unknown>, logge
mappings,
// @ts-expect-error
ingestPipeline,
createPipelines,
data
);
return response.ok({ body: result });
@ -393,6 +395,50 @@ export function fileUploadRoutes(coreSetup: CoreSetup<StartDeps, unknown>, logge
const esClient = (await context.core).elasticsearch.client;
const resp = await previewTikaContents(esClient, base64File);
return response.ok({
body: resp,
});
} catch (e) {
return response.customError(wrapError(e));
}
}
);
/**
* @apiGroup FileDataVisualizer
*
* @api {post} /internal/file_upload/remove_pipelines Remove a list of ingest pipelines
* @apiName RemovePipelines
* @apiDescription Remove a list of ingest pipelines by id
*/
router.versioned
.delete({
path: '/internal/file_upload/remove_pipelines/{pipelineIds}',
access: 'internal',
security: {
authz: {
requiredPrivileges: ['fileUpload:analyzeFile'],
},
},
})
.addVersion(
{
version: '1',
validate: {
request: {
params: schema.object({ pipelineIds: schema.string() }),
},
},
},
async (context, request, response) => {
try {
const { pipelineIds } = request.params;
const esClient = (await context.core).elasticsearch.client;
const resp = await Promise.all(
pipelineIds.split(',').map((id) => esClient.asCurrentUser.ingest.deletePipeline({ id }))
);
return response.ok({
body: resp,
});

View file

@ -30,6 +30,13 @@ export const importFileQuerySchema = schema.object({
id: schema.maybe(schema.string()),
});
const ingestPipeline = schema.maybe(
schema.object({
id: schema.maybe(schema.string()),
pipeline: schema.maybe(schema.any()),
})
);
export const importFileBodySchema = schema.object({
index: schema.string(),
data: schema.arrayOf(schema.any()),
@ -37,12 +44,8 @@ export const importFileBodySchema = schema.object({
/** Mappings */
mappings: schema.any(),
/** Ingest pipeline definition */
ingestPipeline: schema.maybe(
schema.object({
id: schema.maybe(schema.string()),
pipeline: schema.maybe(schema.any()),
})
),
ingestPipeline,
createPipelines: schema.maybe(schema.arrayOf(ingestPipeline)),
});
export const runtimeMappingsSchema = schema.object(

View file

@ -19,6 +19,8 @@ import { mlPluginMock } from '@kbn/ml-plugin/public/mocks';
import { securityMock } from '@kbn/security-plugin/public/mocks';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { uiActionsEnhancedPluginMock } from '@kbn/ui-actions-enhanced-plugin/public/mocks';
import { mockHistory } from '../react_router/state.mock';
export const mockKibanaValues = {
@ -71,6 +73,7 @@ export const mockKibanaValues = {
setDocTitle: jest.fn(),
share: sharePluginMock.createStartContract(),
ml: mlPluginMock.createStartContract(),
uiActions: uiActionsEnhancedPluginMock.createStartContract(),
uiSettings: uiSettingsServiceMock.createStartContract(),
updateSideNavDefinition: jest.fn(),
user: null,

View file

@ -13,6 +13,7 @@ import { useValues } from 'kea';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { OPEN_FILE_UPLOAD_LITE_TRIGGER } from '@kbn/file-upload-common';
import { i18n } from '@kbn/i18n';
import {
@ -46,9 +47,19 @@ export const IngestionSelector: React.FC = () => {
application: { navigateToApp },
config,
productFeatures,
uiActions,
} = useValues(KibanaLogic);
const { errorConnectingMessage } = useValues(HttpLogic);
const crawlerDisabled = Boolean(errorConnectingMessage || !config.host);
const showFileUploadFlyout = React.useCallback(() => {
if (uiActions !== null) {
uiActions.getTrigger(OPEN_FILE_UPLOAD_LITE_TRIGGER).exec({
autoAddInference: '.elser-2-elasticsearch',
});
}
}, [uiActions]);
return (
<>
<EuiFlexGroup>
@ -187,7 +198,7 @@ export const IngestionSelector: React.FC = () => {
defaultMessage: 'Choose a file',
}
)}
onClick={() => navigateToApp('home', { path: '#/tutorial_directory/fileDataViz' })}
onClick={() => showFileUploadFlyout()}
/>
</EuiFlexItem>
<EuiFlexItem>

View file

@ -83,6 +83,7 @@ export const renderApp = (
security,
share,
ml,
uiActions,
} = plugins;
const entCloudHost = getCloudEnterpriseSearchHost(plugins.cloud);
@ -137,6 +138,7 @@ export const renderApp = (
setChromeIsVisible: chrome.setIsVisible,
setDocTitle: chrome.docTitle.change,
share,
uiActions,
uiSettings,
updateSideNavDefinition,
});

View file

@ -32,6 +32,8 @@ import { ConnectorDefinition } from '@kbn/search-connectors';
import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { ClientConfigType, ProductAccess, ProductFeatures } from '../../../../common/types';
import { ESConfig, UpdateSideNavDefinitionFn } from '../../../plugin';
@ -70,6 +72,7 @@ export interface KibanaLogicProps {
setChromeIsVisible(isVisible: boolean): void;
setDocTitle(title: string): void;
share?: SharePluginStart;
uiActions: UiActionsStart;
uiSettings?: IUiSettingsClient;
updateSideNavDefinition: UpdateSideNavDefinitionFn;
}
@ -103,6 +106,7 @@ export interface KibanaValues {
setChromeIsVisible(isVisible: boolean): void;
setDocTitle(title: string): void;
share: SharePluginStart | null;
uiActions: UiActionsStart | null;
uiSettings: IUiSettingsClient | null;
updateSideNavDefinition: UpdateSideNavDefinitionFn;
user: AuthenticatedUser | null;
@ -148,6 +152,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
setChromeIsVisible: [props.setChromeIsVisible, {}],
setDocTitle: [props.setDocTitle, {}],
share: [props.share || null, {}],
uiActions: [props.uiActions, {}],
uiSettings: [props.uiSettings, {}],
updateSideNavDefinition: [props.updateSideNavDefinition, {}],
user: [

View file

@ -26,6 +26,7 @@ import { LensPublicStart } from '@kbn/lens-plugin/public';
import { mlPluginMock } from '@kbn/ml-plugin/public/mocks';
import { securityMock } from '@kbn/security-plugin/public/mocks';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { uiActionsEnhancedPluginMock } from '@kbn/ui-actions-enhanced-plugin/public/mocks';
import { mountHttpLogic } from '../shared/http';
import { mountKibanaLogic, KibanaLogicProps } from '../shared/kibana';
@ -89,6 +90,7 @@ export const mockKibanaProps: KibanaLogicProps = {
setChromeIsVisible: jest.fn(),
setDocTitle: jest.fn(),
share: sharePluginMock.createStartContract(),
uiActions: uiActionsEnhancedPluginMock.createStartContract(),
uiSettings: uiSettingsServiceMock.createStartContract(),
updateSideNavDefinition: jest.fn(),
};

View file

@ -90,5 +90,7 @@
"@kbn/core-deprecations-common",
"@kbn/fleet-plugin",
"@kbn/core-http-request-handler-context-server",
"@kbn/ui-actions-enhanced-plugin",
"@kbn/file-upload-common",
]
}

View file

@ -26,6 +26,7 @@
"security",
"stackConnectors",
"triggersActionsUi",
"uiActions"
],
"optionalPlugins": [
"cloud",

View file

@ -16,6 +16,10 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { EuiForm } from '@elastic/eui';
import { FormProvider, useForm } from 'react-hook-form';
jest.mock('../hooks/use_source_indices_field', () => ({
useSourceIndicesFields: () => ({}),
}));
const MockFormProvider = ({ children }: { children: React.ReactElement }) => {
const methods = useForm({
values: {

View file

@ -23,6 +23,7 @@ import { AnalyticsEvents } from '../../analytics/constants';
import { AddDataSources } from './add_data_sources';
import { ConnectLLMButton } from './connect_llm_button';
import { CreateIndexButton } from './create_index_button';
import { UploadFileButton } from '../upload_file_button';
export const ChatSetupPage: React.FC = () => {
const usageTracker = useUsageTracker();
@ -72,6 +73,9 @@ export const ChatSetupPage: React.FC = () => {
<EuiFlexItem grow={false}>
{indices.length ? <AddDataSources /> : <CreateIndexButton />}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<UploadFileButton isSetup={true} />
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
@ -86,7 +90,12 @@ export const ChatSetupPage: React.FC = () => {
/>
</span>
</EuiTitle>{' '}
<EuiLink href={docLinks.chatPlayground} target="_blank" external>
<EuiLink
data-test-subj="searchPlaygroundChatSetupPageReadDocumentationLink"
href={docLinks.chatPlayground}
target="_blank"
external
>
<FormattedMessage
id="xpack.searchPlayground.setupPage.documentationLink"
defaultMessage="Read documentation"

View file

@ -10,6 +10,7 @@ import React from 'react';
import { DataActionButton } from './data_action_button';
import { ViewCodeAction } from './view_code/view_code_action';
import { PlaygroundPageMode } from '../types';
import { UploadFileButton } from './upload_file_button';
export const Toolbar: React.FC<{ selectedPageMode: PlaygroundPageMode }> = ({
selectedPageMode = PlaygroundPageMode.chat,
@ -17,6 +18,7 @@ export const Toolbar: React.FC<{ selectedPageMode: PlaygroundPageMode }> = ({
return (
<EuiFlexGroup gutterSize="s" alignItems="center" data-test-subj="playground-header-actions">
<DataActionButton />
<UploadFileButton />
<ViewCodeAction selectedPageMode={selectedPageMode} />
</EuiFlexGroup>
);

View file

@ -0,0 +1,52 @@
/*
* 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 React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButton } from '@elastic/eui';
import { type FileUploadResults, OPEN_FILE_UPLOAD_LITE_TRIGGER } from '@kbn/file-upload-common';
import { useKibana } from '../hooks/use_kibana';
import { useSourceIndicesFields } from '../hooks/use_source_indices_field';
interface Props {
isSetup?: boolean;
}
export const UploadFileButton: React.FC<Props> = ({ isSetup }) => {
const {
services: { uiActions },
} = useKibana();
const { setIndices: setSelectedIndices } = useSourceIndicesFields();
const showFileUploadFlyout = React.useCallback(() => {
if (uiActions !== null) {
uiActions.getTrigger(OPEN_FILE_UPLOAD_LITE_TRIGGER).exec({
autoAddInference: '.elser-2-elasticsearch',
onUploadComplete: (results: FileUploadResults) => {
setSelectedIndices([results.index]);
},
});
}
}, [setSelectedIndices, uiActions]);
return (
<>
<EuiButton
size={isSetup ? 'm' : 's'}
fill={isSetup}
iconType="plusInCircle"
onClick={() => showFileUploadFlyout()}
data-test-subj="uploadFileButton"
>
<FormattedMessage
id="xpack.searchPlayground.setupPage.uploadFileLabel"
defaultMessage="Upload file"
/>
</EuiButton>
</>
);
};

View file

@ -25,6 +25,7 @@ import type { SecurityPluginStart } from '@kbn/security-plugin/public';
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { ActionConnector } from '@kbn/alerts-ui-shared/src/common/types';
import type { ServiceProviderKeys } from '@kbn/inference-endpoint-ui-common';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type { ChatRequestData, MessageRole, LLMs } from '../common/types';
export * from '../common/types';
@ -56,6 +57,7 @@ export interface AppPluginStartDependencies {
searchNavigation?: SearchNavigationPluginStart;
security: SecurityPluginStart;
licensing: LicensingPluginStart;
uiActions: UiActionsStart;
}
export type AppServicesContext = CoreStart & AppPluginStartDependencies;

View file

@ -49,6 +49,8 @@
"@kbn/inference-endpoint-ui-common",
"@kbn/inference-common",
"@kbn/alerts-ui-shared",
"@kbn/ui-actions-plugin",
"@kbn/file-upload-common",
],
"exclude": [
"target/**/*",

View file

@ -5777,6 +5777,10 @@
version "0.0.0"
uid ""
"@kbn/file-upload-common@link:x-pack/platform/packages/shared/file-upload-common":
version "0.0.0"
uid ""
"@kbn/file-upload-plugin@link:x-pack/platform/plugins/private/file_upload":
version "0.0.0"
uid ""