Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Transforms: Single Column Wizard. #64436

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 146 additions & 123 deletions x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useEffect, FC } from 'react';
import { isEqual } from 'lodash';
import React, { memo, useEffect, FC } from 'react';

import { i18n } from '@kbn/i18n';

Expand Down Expand Up @@ -50,132 +51,154 @@ function isWithHeader(arg: any): arg is PropsWithHeader {

type Props = PropsWithHeader | PropsWithoutHeader;

export const DataGrid: FC<Props> = props => {
const {
columns,
dataTestSubj,
errorMessage,
invalidSortingColumnns,
noDataMessage,
onChangeItemsPerPage,
onChangePage,
onSort,
pagination,
setVisibleColumns,
renderCellValue,
rowCount,
sortingColumns,
status,
tableItems: data,
toastNotifications,
visibleColumns,
} = props;

useEffect(() => {
if (invalidSortingColumnns.length > 0) {
invalidSortingColumnns.forEach(columnId => {
toastNotifications.addDanger(
i18n.translate('xpack.ml.dataGrid.invalidSortingColumnError', {
defaultMessage: `The column '{columnId}' cannot be used for sorting.`,
values: { columnId },
})
);
});
}
}, [invalidSortingColumnns, toastNotifications]);

if (status === INDEX_STATUS.LOADED && data.length === 0) {
return (
<div data-test-subj={`${dataTestSubj} empty`}>
{isWithHeader(props) && <DataGridTitle title={props.title} />}
<EuiCallOut
title={i18n.translate('xpack.ml.dataGrid.IndexNoDataCalloutTitle', {
defaultMessage: 'Empty index query result.',
})}
color="primary"
>
<p>
{i18n.translate('xpack.ml.dataGrid.IndexNoDataCalloutBody', {
defaultMessage:
'The query for the index returned no results. Please make sure you have sufficient permissions, the index contains documents and your query is not too restrictive.',
export const DataGrid: FC<Props> = memo(
props => {
const {
columns,
dataTestSubj,
errorMessage,
invalidSortingColumnns,
noDataMessage,
onChangeItemsPerPage,
onChangePage,
onSort,
pagination,
setVisibleColumns,
renderCellValue,
rowCount,
sortingColumns,
status,
tableItems: data,
toastNotifications,
visibleColumns,
} = props;

useEffect(() => {
if (invalidSortingColumnns.length > 0) {
invalidSortingColumnns.forEach(columnId => {
toastNotifications.addDanger(
i18n.translate('xpack.ml.dataGrid.invalidSortingColumnError', {
defaultMessage: `The column '{columnId}' cannot be used for sorting.`,
values: { columnId },
})
);
});
}
}, [invalidSortingColumnns, toastNotifications]);

if (status === INDEX_STATUS.LOADED && data.length === 0) {
return (
<div data-test-subj={`${dataTestSubj} empty`}>
{isWithHeader(props) && <DataGridTitle title={props.title} />}
<EuiCallOut
title={i18n.translate('xpack.ml.dataGrid.IndexNoDataCalloutTitle', {
defaultMessage: 'Empty index query result.',
})}
</p>
</EuiCallOut>
</div>
);
}
color="primary"
>
<p>
{i18n.translate('xpack.ml.dataGrid.IndexNoDataCalloutBody', {
defaultMessage:
'The query for the index returned no results. Please make sure you have sufficient permissions, the index contains documents and your query is not too restrictive.',
})}
</p>
</EuiCallOut>
</div>
);
}

if (noDataMessage !== '') {
return (
<div data-test-subj={`${dataTestSubj} empty`}>
{isWithHeader(props) && <DataGridTitle title={props.title} />}
<EuiCallOut
title={i18n.translate('xpack.ml.dataGrid.dataGridNoDataCalloutTitle', {
defaultMessage: 'Index preview not available',
})}
color="primary"
>
<p>{noDataMessage}</p>
</EuiCallOut>
</div>
);
}

return (
<div data-test-subj={`${dataTestSubj} ${status === INDEX_STATUS.ERROR ? 'error' : 'loaded'}`}>
{isWithHeader(props) && (
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>
<DataGridTitle title={props.title} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy
beforeMessage={props.copyToClipboardDescription}
textToCopy={props.copyToClipboard}
>
{(copy: () => void) => (
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={props.copyToClipboardDescription}
/>
)}
</EuiCopy>
</EuiFlexItem>
</EuiFlexGroup>
)}
{status === INDEX_STATUS.ERROR && (
<div data-test-subj={`${dataTestSubj} error`}>
if (noDataMessage !== '') {
return (
<div data-test-subj={`${dataTestSubj} empty`}>
{isWithHeader(props) && <DataGridTitle title={props.title} />}
<EuiCallOut
title={i18n.translate('xpack.ml.dataGrid.indexDataError', {
defaultMessage: 'An error occurred loading the index data.',
title={i18n.translate('xpack.ml.dataGrid.dataGridNoDataCalloutTitle', {
defaultMessage: 'Index preview not available',
})}
color="danger"
iconType="cross"
color="primary"
>
<EuiCodeBlock language="json" fontSize="s" paddingSize="s" isCopyable>
{errorMessage}
</EuiCodeBlock>
<p>{noDataMessage}</p>
</EuiCallOut>
<EuiSpacer size="m" />
</div>
)}
<EuiDataGrid
aria-label={isWithHeader(props) ? props.title : ''}
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
gridStyle={euiDataGridStyle}
rowCount={rowCount}
renderCellValue={renderCellValue}
sorting={{ columns: sortingColumns, onSort }}
toolbarVisibility={euiDataGridToolbarSettings}
pagination={{
...pagination,
pageSizeOptions: [5, 10, 25],
onChangeItemsPerPage,
onChangePage,
}}
/>
</div>
);
};
);
}

return (
<div data-test-subj={`${dataTestSubj} ${status === INDEX_STATUS.ERROR ? 'error' : 'loaded'}`}>
{isWithHeader(props) && (
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>
<DataGridTitle title={props.title} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy
beforeMessage={props.copyToClipboardDescription}
textToCopy={props.copyToClipboard}
>
{(copy: () => void) => (
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={props.copyToClipboardDescription}
/>
)}
</EuiCopy>
</EuiFlexItem>
</EuiFlexGroup>
)}
{status === INDEX_STATUS.ERROR && (
<div data-test-subj={`${dataTestSubj} error`}>
<EuiCallOut
title={i18n.translate('xpack.ml.dataGrid.indexDataError', {
defaultMessage: 'An error occurred loading the index data.',
})}
color="danger"
iconType="cross"
>
<EuiCodeBlock language="json" fontSize="s" paddingSize="s" isCopyable>
{errorMessage}
</EuiCodeBlock>
</EuiCallOut>
<EuiSpacer size="m" />
</div>
)}
<EuiDataGrid
aria-label={isWithHeader(props) ? props.title : ''}
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
gridStyle={euiDataGridStyle}
rowCount={rowCount}
renderCellValue={renderCellValue}
sorting={{ columns: sortingColumns, onSort }}
toolbarVisibility={euiDataGridToolbarSettings}
pagination={{
...pagination,
pageSizeOptions: [5, 10, 25],
onChangeItemsPerPage,
onChangePage,
}}
/>
</div>
);
},
(prevProps, nextProps) => isEqual(pickProps(prevProps), pickProps(nextProps))
darnautov marked this conversation as resolved.
Show resolved Hide resolved
);

function pickProps(props: Props) {
return [
props.columns,
props.dataTestSubj,
props.errorMessage,
props.invalidSortingColumnns,
props.noDataMessage,
props.pagination,
props.rowCount,
props.sortingColumns,
props.status,
props.tableItems,
props.visibleColumns,
...(isWithHeader(props)
? [props.copyToClipboard, props.copyToClipboardDescription, props.title]
: []),
];
}
2 changes: 1 addition & 1 deletion x-pack/plugins/transform/public/app/common/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { PivotGroupByConfig } from '../common';

import { StepDefineExposedState } from '../sections/create_transform/components/step_define/step_define_form';
import { StepDefineExposedState } from '../sections/create_transform/components/step_define';
import { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form';

import { PIVOT_SUPPORTED_GROUP_BY_AGGS } from './pivot_group_by';
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/transform/public/app/common/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DefaultOperator } from 'elasticsearch';
import { dictionaryToArray } from '../../../common/types/common';
import { SavedSearchQuery } from '../hooks/use_search_items';

import { StepDefineExposedState } from '../sections/create_transform/components/step_define/step_define_form';
import { StepDefineExposedState } from '../sections/create_transform/components/step_define';
import { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form';

import { IndexPattern } from '../../../../../../src/plugins/data/public';
Expand Down
3 changes: 1 addition & 2 deletions x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ export const usePivotData = (
tableItems,
} = dataGrid;

const previewRequest = getPreviewRequestBody(indexPatternTitle, query, groupByArr, aggsArr);

const getPreviewData = async () => {
if (aggsArr.length === 0 || groupByArr.length === 0) {
setTableItems([]);
Expand All @@ -142,6 +140,7 @@ export const usePivotData = (
setStatus(INDEX_STATUS.LOADING);

try {
const previewRequest = getPreviewRequestBody(indexPatternTitle, query, groupByArr, aggsArr);
const resp = await api.getTransformsPreview(previewRequest);
setTableItems(resp.preview);
setRowCount(resp.preview.length);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { isEqual } from 'lodash';
import React, { memo, FC } from 'react';

import { EuiCodeEditor, EuiFormRow } from '@elastic/eui';

import { i18n } from '@kbn/i18n';

import { StepDefineFormHook } from '../step_define';

export const AdvancedPivotEditor: FC<StepDefineFormHook['advancedPivotEditor']> = memo(
({
actions: { convertToJson, setAdvancedEditorConfig, setAdvancedPivotEditorApplyButtonEnabled },
state: { advancedEditorConfigLastApplied, advancedEditorConfig, xJsonMode },
}) => {
return (
<EuiFormRow
fullWidth
label={i18n.translate('xpack.transform.stepDefineForm.advancedEditorLabel', {
defaultMessage: 'Pivot configuration object',
})}
>
<EuiCodeEditor
data-test-subj="transformAdvancedPivotEditor"
style={{ border: '1px solid #e3e6ef' }}
height="250px"
width="100%"
mode={xJsonMode}
value={advancedEditorConfig}
onChange={(d: string) => {
setAdvancedEditorConfig(d);

// Disable the "Apply"-Button if the config hasn't changed.
if (advancedEditorConfigLastApplied === d) {
setAdvancedPivotEditorApplyButtonEnabled(false);
return;
}

// Try to parse the string passed on from the editor.
// If parsing fails, the "Apply"-Button will be disabled
try {
JSON.parse(convertToJson(d));
setAdvancedPivotEditorApplyButtonEnabled(true);
} catch (e) {
setAdvancedPivotEditorApplyButtonEnabled(false);
}
}}
setOptions={{
fontSize: '12px',
}}
theme="textmate"
aria-label={i18n.translate('xpack.transform.stepDefineForm.advancedEditorAriaLabel', {
defaultMessage: 'Advanced pivot editor',
})}
/>
</EuiFormRow>
);
},
(prevProps, nextProps) => isEqual(pickProps(prevProps), pickProps(nextProps))
);

function pickProps(props: StepDefineFormHook['advancedPivotEditor']) {
return [props.state.advancedEditorConfigLastApplied, props.state.advancedEditorConfig];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { AdvancedPivotEditor } from './advanced_pivot_editor';
Loading