mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
Fixes applying saved searches in the transform wizard. Previously, upon initializing the transform wizard's state, we would miss passing on the initialized data from kibanaContext. The resulting bug was that saved search were not applied in the generated transform config and source preview table.
This commit is contained in:
parent
65a60c9b64
commit
9eed629e1c
39 changed files with 850 additions and 349 deletions
|
@ -4,5 +4,5 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { ProgressBar, MlInMemoryTable } from './ml_in_memory_table';
|
||||
export { ProgressBar, mlInMemoryTableFactory } from './ml_in_memory_table';
|
||||
export * from './types';
|
||||
|
|
|
@ -71,34 +71,38 @@ const getInitialSorting = (columns: any, sorting: any) => {
|
|||
};
|
||||
};
|
||||
|
||||
import { MlInMemoryTableBasic } from './types';
|
||||
import { mlInMemoryTableBasicFactory } from './types';
|
||||
|
||||
export class MlInMemoryTable extends MlInMemoryTableBasic {
|
||||
static getDerivedStateFromProps(nextProps: any, prevState: any) {
|
||||
const derivedState = {
|
||||
...prevState.prevProps,
|
||||
pageIndex: nextProps.pagination.initialPageIndex,
|
||||
pageSize: nextProps.pagination.initialPageSize,
|
||||
};
|
||||
export function mlInMemoryTableFactory<T>() {
|
||||
const MlInMemoryTableBasic = mlInMemoryTableBasicFactory<T>();
|
||||
|
||||
if (nextProps.items !== prevState.prevProps.items) {
|
||||
Object.assign(derivedState, {
|
||||
prevProps: {
|
||||
items: nextProps.items,
|
||||
},
|
||||
});
|
||||
return class MlInMemoryTable extends MlInMemoryTableBasic {
|
||||
static getDerivedStateFromProps(nextProps: any, prevState: any) {
|
||||
const derivedState = {
|
||||
...prevState.prevProps,
|
||||
pageIndex: nextProps.pagination.initialPageIndex,
|
||||
pageSize: nextProps.pagination.initialPageSize,
|
||||
};
|
||||
|
||||
if (nextProps.items !== prevState.prevProps.items) {
|
||||
Object.assign(derivedState, {
|
||||
prevProps: {
|
||||
items: nextProps.items,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { sortName, sortDirection } = getInitialSorting(nextProps.columns, nextProps.sorting);
|
||||
if (
|
||||
sortName !== prevState.prevProps.sortName ||
|
||||
sortDirection !== prevState.prevProps.sortDirection
|
||||
) {
|
||||
Object.assign(derivedState, {
|
||||
sortName,
|
||||
sortDirection,
|
||||
});
|
||||
}
|
||||
return derivedState;
|
||||
}
|
||||
|
||||
const { sortName, sortDirection } = getInitialSorting(nextProps.columns, nextProps.sorting);
|
||||
if (
|
||||
sortName !== prevState.prevProps.sortName ||
|
||||
sortDirection !== prevState.prevProps.sortDirection
|
||||
) {
|
||||
Object.assign(derivedState, {
|
||||
sortName,
|
||||
sortDirection,
|
||||
});
|
||||
}
|
||||
return derivedState;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,24 +8,21 @@ import { Component, HTMLAttributes, ReactElement, ReactNode } from 'react';
|
|||
|
||||
import { CommonProps, EuiInMemoryTable } from '@elastic/eui';
|
||||
|
||||
// At some point this could maybe solved with a generic <T>.
|
||||
type Item = any;
|
||||
|
||||
// Not using an enum here because the original HorizontalAlignment is also a union type of string.
|
||||
type HorizontalAlignment = 'left' | 'center' | 'right';
|
||||
|
||||
type SortableFunc = (item: Item) => any;
|
||||
type Sortable = boolean | SortableFunc;
|
||||
type SortableFunc<T> = <T>(item: T) => any;
|
||||
type Sortable<T> = boolean | SortableFunc<T>;
|
||||
type DATA_TYPES = any;
|
||||
type FooterFunc = (payload: { items: Item[]; pagination: any }) => ReactNode;
|
||||
type FooterFunc = <T>(payload: { items: T[]; pagination: any }) => ReactNode;
|
||||
type RenderFunc = (value: any, record?: any) => ReactNode;
|
||||
export interface FieldDataColumnType {
|
||||
export interface FieldDataColumnType<T> {
|
||||
field: string;
|
||||
name: ReactNode;
|
||||
description?: string;
|
||||
dataType?: DATA_TYPES;
|
||||
width?: string;
|
||||
sortable?: Sortable;
|
||||
sortable?: Sortable<T>;
|
||||
align?: HorizontalAlignment;
|
||||
truncateText?: boolean;
|
||||
render?: RenderFunc;
|
||||
|
@ -34,38 +31,38 @@ export interface FieldDataColumnType {
|
|||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export interface ComputedColumnType {
|
||||
export interface ComputedColumnType<T> {
|
||||
render: RenderFunc;
|
||||
name?: ReactNode;
|
||||
description?: string;
|
||||
sortable?: (item: Item) => any;
|
||||
sortable?: (item: T) => any;
|
||||
width?: string;
|
||||
truncateText?: boolean;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
type ICON_TYPES = any;
|
||||
type IconTypesFunc = (item: Item) => ICON_TYPES; // (item) => oneOf(ICON_TYPES)
|
||||
type IconTypesFunc<T> = (item: T) => ICON_TYPES; // (item) => oneOf(ICON_TYPES)
|
||||
type BUTTON_ICON_COLORS = any;
|
||||
type ButtonIconColorsFunc = (item: Item) => BUTTON_ICON_COLORS; // (item) => oneOf(ICON_BUTTON_COLORS)
|
||||
interface DefaultItemActionType {
|
||||
type ButtonIconColorsFunc<T> = (item: T) => BUTTON_ICON_COLORS; // (item) => oneOf(ICON_BUTTON_COLORS)
|
||||
interface DefaultItemActionType<T> {
|
||||
type?: 'icon' | 'button';
|
||||
name: string;
|
||||
description: string;
|
||||
onClick?(item: Item): void;
|
||||
onClick?(item: T): void;
|
||||
href?: string;
|
||||
target?: string;
|
||||
available?(item: Item): boolean;
|
||||
enabled?(item: Item): boolean;
|
||||
available?(item: T): boolean;
|
||||
enabled?(item: T): boolean;
|
||||
isPrimary?: boolean;
|
||||
icon?: ICON_TYPES | IconTypesFunc; // required when type is 'icon'
|
||||
color?: BUTTON_ICON_COLORS | ButtonIconColorsFunc;
|
||||
icon?: ICON_TYPES | IconTypesFunc<T>; // required when type is 'icon'
|
||||
color?: BUTTON_ICON_COLORS | ButtonIconColorsFunc<T>;
|
||||
}
|
||||
|
||||
interface CustomItemActionType {
|
||||
render(item: Item, enabled: boolean): ReactNode;
|
||||
available?(item: Item): boolean;
|
||||
enabled?(item: Item): boolean;
|
||||
interface CustomItemActionType<T> {
|
||||
render(item: T, enabled: boolean): ReactNode;
|
||||
available?(item: T): boolean;
|
||||
enabled?(item: T): boolean;
|
||||
isPrimary?: boolean;
|
||||
}
|
||||
|
||||
|
@ -76,20 +73,20 @@ export interface ExpanderColumnType {
|
|||
render: RenderFunc;
|
||||
}
|
||||
|
||||
type SupportedItemActionType = DefaultItemActionType | CustomItemActionType;
|
||||
type SupportedItemActionType<T> = DefaultItemActionType<T> | CustomItemActionType<T>;
|
||||
|
||||
export interface ActionsColumnType {
|
||||
actions: SupportedItemActionType[];
|
||||
export interface ActionsColumnType<T> {
|
||||
actions: Array<SupportedItemActionType<T>>;
|
||||
name?: ReactNode;
|
||||
description?: string;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
export type ColumnType =
|
||||
| ActionsColumnType
|
||||
| ComputedColumnType
|
||||
export type ColumnType<T> =
|
||||
| ActionsColumnType<T>
|
||||
| ComputedColumnType<T>
|
||||
| ExpanderColumnType
|
||||
| FieldDataColumnType;
|
||||
| FieldDataColumnType<T>;
|
||||
|
||||
type QueryType = any;
|
||||
|
||||
|
@ -161,17 +158,17 @@ export interface OnTableChangeArg extends Sorting {
|
|||
page: { index: number; size: number };
|
||||
}
|
||||
|
||||
type ItemIdTypeFunc = (item: Item) => string;
|
||||
type ItemIdTypeFunc = <T>(item: T) => string;
|
||||
type ItemIdType =
|
||||
| string // the name of the item id property
|
||||
| ItemIdTypeFunc;
|
||||
|
||||
export type EuiInMemoryTableProps = CommonProps & {
|
||||
columns: ColumnType[];
|
||||
export type EuiInMemoryTableProps<T> = CommonProps & {
|
||||
columns: Array<ColumnType<T>>;
|
||||
hasActions?: boolean;
|
||||
isExpandable?: boolean;
|
||||
isSelectable?: boolean;
|
||||
items?: Item[];
|
||||
items?: T[];
|
||||
loading?: boolean;
|
||||
message?: HTMLAttributes<HTMLDivElement>;
|
||||
error?: string;
|
||||
|
@ -184,16 +181,18 @@ export type EuiInMemoryTableProps = CommonProps & {
|
|||
responsive?: boolean;
|
||||
selection?: SelectionType;
|
||||
itemId?: ItemIdType;
|
||||
itemIdToExpandedRowMap?: Record<string, Item>;
|
||||
rowProps?: (item: Item) => void | Record<string, any>;
|
||||
itemIdToExpandedRowMap?: Record<string, JSX.Element>;
|
||||
rowProps?: (item: T) => void | Record<string, any>;
|
||||
cellProps?: () => void | Record<string, any>;
|
||||
onTableChange?: (arg: OnTableChangeArg) => void;
|
||||
};
|
||||
|
||||
interface ComponentWithConstructor<T> extends Component {
|
||||
type EuiInMemoryTableType = typeof EuiInMemoryTable;
|
||||
|
||||
interface ComponentWithConstructor<T> extends EuiInMemoryTableType {
|
||||
new (): Component<T>;
|
||||
}
|
||||
|
||||
export const MlInMemoryTableBasic = (EuiInMemoryTable as any) as ComponentWithConstructor<
|
||||
EuiInMemoryTableProps
|
||||
>;
|
||||
export function mlInMemoryTableBasicFactory<T>() {
|
||||
return EuiInMemoryTable as ComponentWithConstructor<EuiInMemoryTableProps<T>>;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json';
|
|||
|
||||
import {
|
||||
ColumnType,
|
||||
MlInMemoryTableBasic,
|
||||
mlInMemoryTableBasicFactory,
|
||||
OnTableChangeArg,
|
||||
SortingPropType,
|
||||
SORT_DIRECTION,
|
||||
|
@ -59,7 +59,7 @@ import {
|
|||
} from '../../../../common';
|
||||
|
||||
import { getOutlierScoreFieldName } from './common';
|
||||
import { useExploreData } from './use_explore_data';
|
||||
import { useExploreData, TableItem } from './use_explore_data';
|
||||
import {
|
||||
DATA_FRAME_TASK_STATE,
|
||||
Query as QueryType,
|
||||
|
@ -167,7 +167,7 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
|
|||
docFieldsCount = docFields.length;
|
||||
}
|
||||
|
||||
const columns: ColumnType[] = [];
|
||||
const columns: Array<ColumnType<TableItem>> = [];
|
||||
|
||||
if (jobConfig !== undefined && selectedFields.length > 0 && tableItems.length > 0) {
|
||||
// table cell color coding takes into account:
|
||||
|
@ -188,7 +188,7 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
|
|||
|
||||
columns.push(
|
||||
...selectedFields.sort(sortColumns(tableItems[0], jobConfig.dest.results_field)).map(k => {
|
||||
const column: ColumnType = {
|
||||
const column: ColumnType<TableItem> = {
|
||||
field: k,
|
||||
name: k,
|
||||
sortable: true,
|
||||
|
@ -425,6 +425,8 @@ export const Exploration: FC<Props> = React.memo(({ jobId, jobStatus }) => {
|
|||
});
|
||||
}
|
||||
|
||||
const MlInMemoryTableBasic = mlInMemoryTableBasicFactory<TableItem>();
|
||||
|
||||
return (
|
||||
<EuiPanel grow={false}>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" responsive={false}>
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
import { getOutlierScoreFieldName } from './common';
|
||||
import { SavedSearchQuery } from '../../../../../contexts/kibana';
|
||||
|
||||
type TableItem = Record<string, any>;
|
||||
export type TableItem = Record<string, any>;
|
||||
|
||||
interface LoadExploreDataArg {
|
||||
field: string;
|
||||
|
|
|
@ -30,7 +30,7 @@ import { Query as QueryType } from '../../../analytics_management/components/ana
|
|||
|
||||
import {
|
||||
ColumnType,
|
||||
MlInMemoryTableBasic,
|
||||
mlInMemoryTableBasicFactory,
|
||||
OnTableChangeArg,
|
||||
SortingPropType,
|
||||
SORT_DIRECTION,
|
||||
|
@ -55,7 +55,7 @@ import {
|
|||
import { getTaskStateBadge } from '../../../analytics_management/components/analytics_list/columns';
|
||||
import { DATA_FRAME_TASK_STATE } from '../../../analytics_management/components/analytics_list/common';
|
||||
|
||||
import { useExploreData } from './use_explore_data';
|
||||
import { useExploreData, TableItem } from './use_explore_data';
|
||||
import { ExplorationTitle } from './regression_exploration';
|
||||
|
||||
const PAGE_SIZE_OPTIONS = [5, 10, 25, 50];
|
||||
|
@ -108,12 +108,12 @@ export const ResultsTable: FC<Props> = React.memo(
|
|||
docFieldsCount = docFields.length;
|
||||
}
|
||||
|
||||
const columns: ColumnType[] = [];
|
||||
const columns: Array<ColumnType<TableItem>> = [];
|
||||
|
||||
if (jobConfig !== undefined && selectedFields.length > 0 && tableItems.length > 0) {
|
||||
columns.push(
|
||||
...selectedFields.sort(sortRegressionResultsColumns(tableItems[0], jobConfig)).map(k => {
|
||||
const column: ColumnType = {
|
||||
const column: ColumnType<TableItem> = {
|
||||
field: k,
|
||||
name: k,
|
||||
sortable: true,
|
||||
|
@ -363,6 +363,8 @@ export const ResultsTable: FC<Props> = React.memo(
|
|||
? errorMessage
|
||||
: searchError;
|
||||
|
||||
const MlInMemoryTableBasic = mlInMemoryTableBasicFactory<TableItem>();
|
||||
|
||||
return (
|
||||
<EuiPanel grow={false}>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" responsive={false}>
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
SearchQuery,
|
||||
} from '../../../../common';
|
||||
|
||||
type TableItem = Record<string, any>;
|
||||
export type TableItem = Record<string, any>;
|
||||
|
||||
interface LoadExploreDataArg {
|
||||
field: string;
|
||||
|
|
|
@ -34,7 +34,7 @@ import { getColumns } from './columns';
|
|||
import { ExpandedRow } from './expanded_row';
|
||||
import {
|
||||
ProgressBar,
|
||||
MlInMemoryTable,
|
||||
mlInMemoryTableFactory,
|
||||
OnTableChangeArg,
|
||||
SortDirection,
|
||||
SORT_DIRECTION,
|
||||
|
@ -326,6 +326,8 @@ export const DataFrameAnalyticsList: FC<Props> = ({
|
|||
setSortDirection(direction);
|
||||
};
|
||||
|
||||
const MlInMemoryTable = mlInMemoryTableFactory<DataFrameAnalyticsListRow>();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { FC, useState } from 'react';
|
|||
import { EuiBadge } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
MlInMemoryTable,
|
||||
mlInMemoryTableFactory,
|
||||
SortDirection,
|
||||
SORT_DIRECTION,
|
||||
OnTableChangeArg,
|
||||
|
@ -27,7 +27,7 @@ import { AnalyticsViewAction } from '../../../data_frame_analytics/pages/analyti
|
|||
import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils';
|
||||
|
||||
interface Props {
|
||||
items: any[];
|
||||
items: DataFrameAnalyticsListRow[];
|
||||
}
|
||||
export const AnalyticsTable: FC<Props> = ({ items }) => {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
|
@ -37,7 +37,7 @@ export const AnalyticsTable: FC<Props> = ({ items }) => {
|
|||
const [sortDirection, setSortDirection] = useState<SortDirection>(SORT_DIRECTION.ASC);
|
||||
|
||||
// id, type, status, progress, created time, view icon
|
||||
const columns: ColumnType[] = [
|
||||
const columns: Array<ColumnType<DataFrameAnalyticsListRow>> = [
|
||||
{
|
||||
field: DataFrameAnalyticsListColumn.id,
|
||||
name: i18n.translate('xpack.ml.overview.analyticsList.id', { defaultMessage: 'ID' }),
|
||||
|
@ -113,6 +113,8 @@ export const AnalyticsTable: FC<Props> = ({ items }) => {
|
|||
},
|
||||
};
|
||||
|
||||
const MlInMemoryTable = mlInMemoryTableFactory<DataFrameAnalyticsListRow>();
|
||||
|
||||
return (
|
||||
<MlInMemoryTable
|
||||
allowNeutralSort={false}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
MlInMemoryTable,
|
||||
mlInMemoryTableFactory,
|
||||
SortDirection,
|
||||
SORT_DIRECTION,
|
||||
OnTableChangeArg,
|
||||
|
@ -59,7 +59,7 @@ export const AnomalyDetectionTable: FC<Props> = ({ items, jobsList, statsBarData
|
|||
const [sortDirection, setSortDirection] = useState<SortDirection>(SORT_DIRECTION.ASC);
|
||||
|
||||
// columns: group, max anomaly, jobs in group, latest timestamp, docs processed, action to explorer
|
||||
const columns: ColumnType[] = [
|
||||
const columns: Array<ColumnType<Group>> = [
|
||||
{
|
||||
field: AnomalyDetectionListColumns.id,
|
||||
name: i18n.translate('xpack.ml.overview.anomalyDetection.tableId', {
|
||||
|
@ -195,6 +195,8 @@ export const AnomalyDetectionTable: FC<Props> = ({ items, jobsList, statsBarData
|
|||
},
|
||||
};
|
||||
|
||||
const MlInMemoryTable = mlInMemoryTableFactory<Group>();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
*/
|
||||
|
||||
export {
|
||||
isKibanaContextInitialized,
|
||||
useKibanaContext,
|
||||
InitializedKibanaContextValue,
|
||||
KibanaContext,
|
||||
KibanaContextValue,
|
||||
SavedSearchQuery,
|
||||
RenderOnlyWithInitializedKibanaContext,
|
||||
} from './kibana_context';
|
||||
export { KibanaProvider } from './kibana_provider';
|
||||
export { useCurrentIndexPattern } from './use_current_index_pattern';
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
import React, { createContext, useContext, FC } from 'react';
|
||||
|
||||
import {
|
||||
IndexPattern as IndexPatternType,
|
||||
|
@ -15,14 +15,15 @@ import { SavedSearch } from '../../../../../../../../src/legacy/core_plugins/kib
|
|||
import { KibanaConfig } from '../../../../../../../../src/legacy/server/kbn_server';
|
||||
|
||||
// set() method is missing in original d.ts
|
||||
export interface KibanaConfigTypeFix extends KibanaConfig {
|
||||
interface KibanaConfigTypeFix extends KibanaConfig {
|
||||
set(key: string, value: any): void;
|
||||
}
|
||||
|
||||
interface UninitializedKibanaContextValue {
|
||||
initialized: boolean;
|
||||
}
|
||||
interface InitializedKibanaContextValue {
|
||||
|
||||
export interface InitializedKibanaContextValue {
|
||||
combinedQuery: any;
|
||||
currentIndexPattern: IndexPatternType;
|
||||
currentSavedSearch: SavedSearch;
|
||||
|
@ -41,3 +42,37 @@ export function isKibanaContextInitialized(arg: any): arg is InitializedKibanaCo
|
|||
export type SavedSearchQuery = object;
|
||||
|
||||
export const KibanaContext = createContext<KibanaContextValue>({ initialized: false });
|
||||
|
||||
/**
|
||||
* Custom hook to get the current kibanaContext.
|
||||
*
|
||||
* @remarks
|
||||
* This hook should only be used in components wrapped in `RenderOnlyWithInitializedKibanaContext`,
|
||||
* otherwise it will throw an error when KibanaContext hasn't been initialized yet.
|
||||
* In return you get the benefit of not having to check if it's been initialized in the component
|
||||
* where it's used.
|
||||
*
|
||||
* @returns `kibanaContext`
|
||||
*/
|
||||
export const useKibanaContext = () => {
|
||||
const kibanaContext = useContext(KibanaContext);
|
||||
|
||||
if (!isKibanaContextInitialized(kibanaContext)) {
|
||||
throw new Error('useKibanaContext: kibanaContext not initialized');
|
||||
}
|
||||
|
||||
return kibanaContext;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper component to render children only if `kibanaContext` has been initialized.
|
||||
* In combination with `useKibanaContext` this avoids having to check for the initialization
|
||||
* in consuming components.
|
||||
*
|
||||
* @returns `children` or `null` depending on whether `kibanaContext` is initialized or not.
|
||||
*/
|
||||
export const RenderOnlyWithInitializedKibanaContext: FC = ({ children }) => {
|
||||
const kibanaContext = useContext(KibanaContext);
|
||||
|
||||
return isKibanaContextInitialized(kibanaContext) ? <>{children}</> : null;
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ export const useCurrentIndexPattern = () => {
|
|||
const context = useContext(KibanaContext);
|
||||
|
||||
if (!isKibanaContextInitialized(context)) {
|
||||
throw new Error('currentIndexPattern is undefined');
|
||||
throw new Error('useCurrentIndexPattern: kibanaContext not initialized');
|
||||
}
|
||||
|
||||
return context.currentIndexPattern;
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
|
||||
import {
|
||||
ColumnType,
|
||||
MlInMemoryTableBasic,
|
||||
mlInMemoryTableBasicFactory,
|
||||
SortingPropType,
|
||||
SORT_DIRECTION,
|
||||
} from '../../../../../shared_imports';
|
||||
|
@ -183,8 +183,8 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ cellClick, quer
|
|||
docFieldsCount = docFields.length;
|
||||
}
|
||||
|
||||
const columns: ColumnType[] = selectedFields.map(k => {
|
||||
const column: ColumnType = {
|
||||
const columns: Array<ColumnType<EsDoc>> = selectedFields.map(k => {
|
||||
const column: ColumnType<EsDoc> = {
|
||||
field: `_source["${k}"]`,
|
||||
name: k,
|
||||
sortable: true,
|
||||
|
@ -319,6 +319,8 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ cellClick, quer
|
|||
defaultMessage: 'Copy Dev Console statement of the source index preview to the clipboard.',
|
||||
});
|
||||
|
||||
const MlInMemoryTableBasic = mlInMemoryTableBasicFactory<EsDoc>();
|
||||
|
||||
return (
|
||||
<EuiPanel grow={false} data-test-subj="transformSourceIndexPreview loaded">
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
|
@ -410,6 +412,9 @@ export const SourceIndexPreview: React.FC<Props> = React.memo(({ cellClick, quer
|
|||
itemId="_id"
|
||||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
isExpandable={true}
|
||||
rowProps={item => ({
|
||||
'data-test-subj': `transformSourceIndexPreviewRow row-${item._id}`,
|
||||
})}
|
||||
sorting={sorting}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, FC, useContext, useEffect, useState } from 'react';
|
||||
import React, { Fragment, FC, useEffect, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
|
@ -32,7 +32,7 @@ import {
|
|||
import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ToastNotificationText } from '../../../../components';
|
||||
import { useApi } from '../../../../hooks/use_api';
|
||||
import { isKibanaContextInitialized, KibanaContext } from '../../../../lib/kibana';
|
||||
import { useKibanaContext } from '../../../../lib/kibana';
|
||||
import { RedirectToTransformManagement } from '../../../../common/navigation';
|
||||
import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants';
|
||||
|
||||
|
@ -73,7 +73,7 @@ export const StepCreateForm: FC<Props> = React.memo(
|
|||
undefined
|
||||
);
|
||||
|
||||
const kibanaContext = useContext(KibanaContext);
|
||||
const kibanaContext = useKibanaContext();
|
||||
|
||||
useEffect(() => {
|
||||
onChange({ created, started, indexPatternId });
|
||||
|
@ -83,10 +83,6 @@ export const StepCreateForm: FC<Props> = React.memo(
|
|||
|
||||
const api = useApi();
|
||||
|
||||
if (!isKibanaContextInitialized(kibanaContext)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function createTransform() {
|
||||
setCreated(true);
|
||||
|
||||
|
@ -151,8 +147,8 @@ export const StepCreateForm: FC<Props> = React.memo(
|
|||
}
|
||||
|
||||
async function createAndStartTransform() {
|
||||
const success = await createTransform();
|
||||
if (success) {
|
||||
const acknowledged = await createTransform();
|
||||
if (acknowledged) {
|
||||
await startTransform();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,11 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ColumnType, MlInMemoryTableBasic, SORT_DIRECTION } from '../../../../../shared_imports';
|
||||
import {
|
||||
ColumnType,
|
||||
mlInMemoryTableBasicFactory,
|
||||
SORT_DIRECTION,
|
||||
} from '../../../../../shared_imports';
|
||||
import { dictionaryToArray } from '../../../../../../common/types/common';
|
||||
import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public';
|
||||
import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils';
|
||||
|
@ -38,7 +42,7 @@ import {
|
|||
} from '../../../../common';
|
||||
|
||||
import { getPivotPreviewDevConsoleStatement } from './common';
|
||||
import { PIVOT_PREVIEW_STATUS, usePivotPreviewData } from './use_pivot_preview_data';
|
||||
import { PreviewItem, PIVOT_PREVIEW_STATUS, usePivotPreviewData } from './use_pivot_preview_data';
|
||||
|
||||
function sortColumns(groupByArr: PivotGroupByConfig[]) {
|
||||
return (a: string, b: string) => {
|
||||
|
@ -210,7 +214,7 @@ export const PivotPreview: FC<PivotPreviewProps> = React.memo(({ aggs, groupBy,
|
|||
columnKeys.sort(sortColumns(groupByArr));
|
||||
|
||||
const columns = columnKeys.map(k => {
|
||||
const column: ColumnType = {
|
||||
const column: ColumnType<PreviewItem> = {
|
||||
field: k,
|
||||
name: k,
|
||||
sortable: true,
|
||||
|
@ -256,6 +260,8 @@ export const PivotPreview: FC<PivotPreviewProps> = React.memo(({ aggs, groupBy,
|
|||
},
|
||||
};
|
||||
|
||||
const MlInMemoryTableBasic = mlInMemoryTableBasicFactory<PreviewItem>();
|
||||
|
||||
return (
|
||||
<EuiPanel data-test-subj="transformPivotPreview loaded">
|
||||
<PreviewTitle previewRequest={previewRequest} />
|
||||
|
@ -273,6 +279,9 @@ export const PivotPreview: FC<PivotPreviewProps> = React.memo(({ aggs, groupBy,
|
|||
initialPageSize: 5,
|
||||
pageSizeOptions: [5, 10, 25],
|
||||
}}
|
||||
rowProps={() => ({
|
||||
'data-test-subj': 'transformPivotPreviewRow',
|
||||
})}
|
||||
sorting={sorting}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, FC, useContext, useEffect, useState } from 'react';
|
||||
import React, { Fragment, FC, useEffect, useState } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -37,9 +37,8 @@ import { KqlFilterBar } from '../../../../../shared_imports';
|
|||
import { SwitchModal } from './switch_modal';
|
||||
|
||||
import {
|
||||
isKibanaContextInitialized,
|
||||
KibanaContext,
|
||||
KibanaContextValue,
|
||||
useKibanaContext,
|
||||
InitializedKibanaContextValue,
|
||||
SavedSearchQuery,
|
||||
} from '../../../../lib/kibana';
|
||||
|
||||
|
@ -75,7 +74,7 @@ const defaultSearch = '*';
|
|||
const emptySearch = '';
|
||||
|
||||
export function getDefaultStepDefineState(
|
||||
kibanaContext: KibanaContextValue
|
||||
kibanaContext: InitializedKibanaContextValue
|
||||
): StepDefineExposedState {
|
||||
return {
|
||||
aggList: {} as PivotAggsConfigDict,
|
||||
|
@ -83,13 +82,9 @@ export function getDefaultStepDefineState(
|
|||
isAdvancedPivotEditorEnabled: false,
|
||||
isAdvancedSourceEditorEnabled: false,
|
||||
searchString:
|
||||
isKibanaContextInitialized(kibanaContext) && kibanaContext.currentSavedSearch !== undefined
|
||||
? kibanaContext.combinedQuery
|
||||
: defaultSearch,
|
||||
kibanaContext.currentSavedSearch !== undefined ? kibanaContext.combinedQuery : defaultSearch,
|
||||
searchQuery:
|
||||
isKibanaContextInitialized(kibanaContext) && kibanaContext.currentSavedSearch !== undefined
|
||||
? kibanaContext.combinedQuery
|
||||
: defaultSearch,
|
||||
kibanaContext.currentSavedSearch !== undefined ? kibanaContext.combinedQuery : defaultSearch,
|
||||
sourceConfigUpdated: false,
|
||||
valid: false,
|
||||
};
|
||||
|
@ -196,7 +191,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange }) => {
|
||||
const kibanaContext = useContext(KibanaContext);
|
||||
const kibanaContext = useKibanaContext();
|
||||
|
||||
const defaults = { ...getDefaultStepDefineState(kibanaContext), ...overrides };
|
||||
|
||||
|
@ -224,10 +219,6 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange
|
|||
// The list of selected group by fields
|
||||
const [groupByList, setGroupByList] = useState(defaults.groupByList);
|
||||
|
||||
if (!isKibanaContextInitialized(kibanaContext)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const indexPattern = kibanaContext.currentIndexPattern;
|
||||
|
||||
const {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useContext, Fragment, FC } from 'react';
|
||||
import React, { Fragment, FC } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -17,7 +17,7 @@ import {
|
|||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { isKibanaContextInitialized, KibanaContext } from '../../../../lib/kibana';
|
||||
import { useKibanaContext } from '../../../../lib/kibana';
|
||||
|
||||
import { AggListSummary } from '../aggregation_list';
|
||||
import { GroupByListSummary } from '../group_by_list';
|
||||
|
@ -35,11 +35,7 @@ export const StepDefineSummary: FC<StepDefineExposedState> = ({
|
|||
groupByList,
|
||||
aggList,
|
||||
}) => {
|
||||
const kibanaContext = useContext(KibanaContext);
|
||||
|
||||
if (!isKibanaContextInitialized(kibanaContext)) {
|
||||
return null;
|
||||
}
|
||||
const kibanaContext = useKibanaContext();
|
||||
|
||||
const pivotQuery = getPivotQuery(searchQuery);
|
||||
let useCodeBlock = false;
|
||||
|
|
|
@ -32,7 +32,8 @@ interface EsMappingType {
|
|||
type: ES_FIELD_TYPES;
|
||||
}
|
||||
|
||||
type PreviewData = Array<Dictionary<any>>;
|
||||
export type PreviewItem = Dictionary<any>;
|
||||
type PreviewData = PreviewItem[];
|
||||
interface PreviewMappings {
|
||||
properties: Dictionary<EsMappingType>;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, FC, useContext, useEffect, useState } from 'react';
|
||||
import React, { Fragment, FC, useEffect, useState } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { metadata } from 'ui/metadata';
|
||||
|
@ -13,7 +13,7 @@ import { toastNotifications } from 'ui/notify';
|
|||
import { EuiLink, EuiSwitch, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
|
||||
import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { isKibanaContextInitialized, KibanaContext } from '../../../../lib/kibana';
|
||||
import { useKibanaContext } from '../../../../lib/kibana';
|
||||
import { isValidIndexName } from '../../../../../../common/utils/es_utils';
|
||||
|
||||
import { ToastNotificationText } from '../../../../components';
|
||||
|
@ -55,7 +55,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export const StepDetailsForm: FC<Props> = React.memo(({ overrides = {}, onChange }) => {
|
||||
const kibanaContext = useContext(KibanaContext);
|
||||
const kibanaContext = useKibanaContext();
|
||||
|
||||
const defaults = { ...getDefaultStepDetailsState(), ...overrides };
|
||||
|
||||
|
@ -80,56 +80,47 @@ export const StepDetailsForm: FC<Props> = React.memo(({ overrides = {}, onChange
|
|||
useEffect(() => {
|
||||
// use an IIFE to avoid returning a Promise to useEffect.
|
||||
(async function() {
|
||||
if (isKibanaContextInitialized(kibanaContext)) {
|
||||
try {
|
||||
setTransformIds(
|
||||
(await api.getTransforms()).transforms.map(
|
||||
(transform: TransformPivotConfig) => transform.id
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', {
|
||||
defaultMessage: 'An error occurred getting the existing transform IDs:',
|
||||
}),
|
||||
text: toMountPoint(<ToastNotificationText text={e} />),
|
||||
});
|
||||
}
|
||||
try {
|
||||
setTransformIds(
|
||||
(await api.getTransforms()).transforms.map(
|
||||
(transform: TransformPivotConfig) => transform.id
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', {
|
||||
defaultMessage: 'An error occurred getting the existing transform IDs:',
|
||||
}),
|
||||
text: toMountPoint(<ToastNotificationText text={e} />),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
setIndexNames((await api.getIndices()).map(index => index.name));
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', {
|
||||
defaultMessage: 'An error occurred getting the existing index names:',
|
||||
}),
|
||||
text: toMountPoint(<ToastNotificationText text={e} />),
|
||||
});
|
||||
}
|
||||
try {
|
||||
setIndexNames((await api.getIndices()).map(index => index.name));
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', {
|
||||
defaultMessage: 'An error occurred getting the existing index names:',
|
||||
}),
|
||||
text: toMountPoint(<ToastNotificationText text={e} />),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
setIndexPatternTitles(await kibanaContext.indexPatterns.getTitles());
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate(
|
||||
'xpack.transform.stepDetailsForm.errorGettingIndexPatternTitles',
|
||||
{
|
||||
defaultMessage: 'An error occurred getting the existing index pattern titles:',
|
||||
}
|
||||
),
|
||||
text: toMountPoint(<ToastNotificationText text={e} />),
|
||||
});
|
||||
}
|
||||
try {
|
||||
setIndexPatternTitles(await kibanaContext.indexPatterns.getTitles());
|
||||
} catch (e) {
|
||||
toastNotifications.addDanger({
|
||||
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexPatternTitles', {
|
||||
defaultMessage: 'An error occurred getting the existing index pattern titles:',
|
||||
}),
|
||||
text: toMountPoint(<ToastNotificationText text={e} />),
|
||||
});
|
||||
}
|
||||
})();
|
||||
// custom comparison
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [kibanaContext.initialized]);
|
||||
|
||||
if (!isKibanaContextInitialized(kibanaContext)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dateFieldNames = kibanaContext.currentIndexPattern.fields
|
||||
.filter(f => f.type === 'date')
|
||||
.map(f => f.name)
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, FC, useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { Fragment, FC, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiSteps, EuiStepStatus } from '@elastic/eui';
|
||||
|
||||
import { isKibanaContextInitialized, KibanaContext } from '../../../../lib/kibana';
|
||||
import { useKibanaContext } from '../../../../lib/kibana';
|
||||
|
||||
import { getCreateRequestBody } from '../../../../common';
|
||||
|
||||
|
@ -68,7 +68,7 @@ const StepDefine: FC<DefinePivotStepProps> = ({
|
|||
};
|
||||
|
||||
export const Wizard: FC = React.memo(() => {
|
||||
const kibanaContext = useContext(KibanaContext);
|
||||
const kibanaContext = useKibanaContext();
|
||||
|
||||
// The current WIZARD_STEP
|
||||
const [currentStep, setCurrentStep] = useState(WIZARD_STEPS.DEFINE);
|
||||
|
@ -108,11 +108,6 @@ export const Wizard: FC = React.memo(() => {
|
|||
}
|
||||
}, []);
|
||||
|
||||
if (!isKibanaContextInitialized(kibanaContext)) {
|
||||
// TODO proper loading indicator
|
||||
return null;
|
||||
}
|
||||
|
||||
const indexPattern = kibanaContext.currentIndexPattern;
|
||||
|
||||
const transformConfig = getCreateRequestBody(
|
||||
|
@ -134,18 +129,6 @@ export const Wizard: FC = React.memo(() => {
|
|||
<StepCreateSummary />
|
||||
);
|
||||
|
||||
// scroll to the currently selected wizard step
|
||||
/*
|
||||
function scrollToRef() {
|
||||
if (definePivotRef !== null && definePivotRef.current !== null) {
|
||||
// TODO Fix types
|
||||
const dummy = definePivotRef as any;
|
||||
const headerOffset = 70;
|
||||
window.scrollTo(0, dummy.current.offsetTop - headerOffset);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const stepsConfig = [
|
||||
{
|
||||
title: i18n.translate('xpack.transform.transformsWizard.stepDefineTitle', {
|
||||
|
@ -171,7 +154,6 @@ export const Wizard: FC = React.memo(() => {
|
|||
<WizardNav
|
||||
previous={() => {
|
||||
setCurrentStep(WIZARD_STEPS.DEFINE);
|
||||
// scrollToRef();
|
||||
}}
|
||||
next={() => setCurrentStep(WIZARD_STEPS.CREATE)}
|
||||
nextActive={stepDetailsState.valid}
|
||||
|
|
|
@ -26,7 +26,7 @@ import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/cons
|
|||
import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation';
|
||||
import { documentationLinksService } from '../../services/documentation';
|
||||
import { PrivilegesWrapper } from '../../lib/authorization';
|
||||
import { KibanaProvider } from '../../lib/kibana';
|
||||
import { KibanaProvider, RenderOnlyWithInitializedKibanaContext } from '../../lib/kibana';
|
||||
|
||||
import { Wizard } from './components/wizard';
|
||||
|
||||
|
@ -82,7 +82,9 @@ export const CreateTransformSection: FC<Props> = ({ match }) => {
|
|||
</EuiTitle>
|
||||
<EuiPageContentBody>
|
||||
<EuiSpacer size="l" />
|
||||
<Wizard />
|
||||
<RenderOnlyWithInitializedKibanaContext>
|
||||
<Wizard />
|
||||
</RenderOnlyWithInitializedKibanaContext>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
|
|
|
@ -90,7 +90,8 @@ exports[`Transform: Transform List <ExpandedRow /> Minimal initialization 1`] =
|
|||
]
|
||||
}
|
||||
/>,
|
||||
"id": "transform-details",
|
||||
"data-test-subj": "transformDetailsTab",
|
||||
"id": "transform-details-tab-fq_date_histogram_1m_1441",
|
||||
"name": "Transform details",
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +189,8 @@ exports[`Transform: Transform List <ExpandedRow /> Minimal initialization 1`] =
|
|||
]
|
||||
}
|
||||
/>,
|
||||
"id": "transform-details",
|
||||
"data-test-subj": "transformDetailsTab",
|
||||
"id": "transform-details-tab-fq_date_histogram_1m_1441",
|
||||
"name": "Transform details",
|
||||
},
|
||||
Object {
|
||||
|
@ -229,14 +231,16 @@ exports[`Transform: Transform List <ExpandedRow /> Minimal initialization 1`] =
|
|||
}
|
||||
}
|
||||
/>,
|
||||
"id": "transform-json",
|
||||
"data-test-subj": "transformJsonTab",
|
||||
"id": "transform-json-tab-fq_date_histogram_1m_1441",
|
||||
"name": "JSON",
|
||||
},
|
||||
Object {
|
||||
"content": <ExpandedRowMessagesPane
|
||||
transformId="fq_date_histogram_1m_1441"
|
||||
/>,
|
||||
"id": "transform-messages",
|
||||
"data-test-subj": "transformMessagesTab",
|
||||
"id": "transform-messages-tab-fq_date_histogram_1m_1441",
|
||||
"name": "Messages",
|
||||
},
|
||||
Object {
|
||||
|
@ -277,7 +281,8 @@ exports[`Transform: Transform List <ExpandedRow /> Minimal initialization 1`] =
|
|||
}
|
||||
}
|
||||
/>,
|
||||
"id": "transform-preview",
|
||||
"data-test-subj": "transformPreviewTab",
|
||||
"id": "transform-preview-tab-fq_date_histogram_1m_1441",
|
||||
"name": "Preview",
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,40 +1,44 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Transform: Job List Expanded Row <ExpandedRowDetailsPane /> Minimal initialization 1`] = `
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
style={
|
||||
Object {
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<Section
|
||||
section={
|
||||
<div
|
||||
data-test-subj="transformDetailsTabContent"
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem
|
||||
style={
|
||||
Object {
|
||||
"items": Array [
|
||||
Object {
|
||||
"description": "the-item-description",
|
||||
"title": "the-item-title",
|
||||
},
|
||||
],
|
||||
"position": "left",
|
||||
"title": "the-section-title",
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<Section
|
||||
section={
|
||||
Object {
|
||||
"items": Array [
|
||||
Object {
|
||||
"description": "the-item-description",
|
||||
"title": "the-item-title",
|
||||
},
|
||||
],
|
||||
"position": "left",
|
||||
"title": "the-section-title",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
style={
|
||||
Object {
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
style={
|
||||
Object {
|
||||
"width": "50%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Transform: Job List Expanded Row <Section /> Minimal initialization 1`] = `
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Transform: Transform List Expanded Row <ExpandedRowJsonPane /> Minimal initialization 1`] = `
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiCodeEditor
|
||||
mode="json"
|
||||
readOnly={true}
|
||||
setOptions={Object {}}
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
<div
|
||||
data-test-subj="transformJsonTabContent"
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<EuiCodeEditor
|
||||
mode="json"
|
||||
readOnly={true}
|
||||
setOptions={Object {}}
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
}
|
||||
theme="textmate"
|
||||
value="{
|
||||
theme="textmate"
|
||||
value="{
|
||||
\\"id\\": \\"fq_date_histogram_1m_1441\\",
|
||||
\\"source\\": {
|
||||
\\"index\\": [
|
||||
|
@ -49,12 +52,13 @@ exports[`Transform: Transform List Expanded Row <ExpandedRowJsonPane /> Minimal
|
|||
\\"version\\": \\"8.0.0\\",
|
||||
\\"create_time\\": 1564388146667
|
||||
}"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -88,14 +88,14 @@ export const getColumns = (
|
|||
|
||||
const columns: [
|
||||
ExpanderColumnType,
|
||||
FieldDataColumnType,
|
||||
FieldDataColumnType,
|
||||
FieldDataColumnType,
|
||||
FieldDataColumnType,
|
||||
ComputedColumnType,
|
||||
ComputedColumnType,
|
||||
ComputedColumnType,
|
||||
ActionsColumnType
|
||||
FieldDataColumnType<TransformListRow>,
|
||||
FieldDataColumnType<TransformListRow>,
|
||||
FieldDataColumnType<TransformListRow>,
|
||||
FieldDataColumnType<TransformListRow>,
|
||||
ComputedColumnType<TransformListRow>,
|
||||
ComputedColumnType<TransformListRow>,
|
||||
ComputedColumnType<TransformListRow>,
|
||||
ActionsColumnType<TransformListRow>
|
||||
] = [
|
||||
{
|
||||
align: RIGHT_ALIGNMENT,
|
||||
|
|
|
@ -121,7 +121,8 @@ export const ExpandedRow: FC<Props> = ({ item }) => {
|
|||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'transform-details',
|
||||
id: `transform-details-tab-${item.id}`,
|
||||
'data-test-subj': 'transformDetailsTab',
|
||||
name: i18n.translate(
|
||||
'xpack.transform.transformList.transformDetails.tabs.transformSettingsLabel',
|
||||
{
|
||||
|
@ -131,12 +132,14 @@ export const ExpandedRow: FC<Props> = ({ item }) => {
|
|||
content: <ExpandedRowDetailsPane sections={[state, checkpointing, stats]} />,
|
||||
},
|
||||
{
|
||||
id: 'transform-json',
|
||||
id: `transform-json-tab-${item.id}`,
|
||||
'data-test-subj': 'transformJsonTab',
|
||||
name: 'JSON',
|
||||
content: <ExpandedRowJsonPane json={item.config} />,
|
||||
},
|
||||
{
|
||||
id: 'transform-messages',
|
||||
id: `transform-messages-tab-${item.id}`,
|
||||
'data-test-subj': 'transformMessagesTab',
|
||||
name: i18n.translate(
|
||||
'xpack.transform.transformList.transformDetails.tabs.transformMessagesLabel',
|
||||
{
|
||||
|
@ -146,7 +149,8 @@ export const ExpandedRow: FC<Props> = ({ item }) => {
|
|||
content: <ExpandedRowMessagesPane transformId={item.id} />,
|
||||
},
|
||||
{
|
||||
id: 'transform-preview',
|
||||
id: `transform-preview-tab-${item.id}`,
|
||||
'data-test-subj': 'transformPreviewTab',
|
||||
name: i18n.translate(
|
||||
'xpack.transform.transformList.transformDetails.tabs.transformPreviewLabel',
|
||||
{
|
||||
|
|
|
@ -50,27 +50,29 @@ interface ExpandedRowDetailsPaneProps {
|
|||
|
||||
export const ExpandedRowDetailsPane: FC<ExpandedRowDetailsPaneProps> = ({ sections }) => {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ width: '50%' }}>
|
||||
{sections
|
||||
.filter(s => s.position === 'left')
|
||||
.map(s => (
|
||||
<Fragment key={s.title}>
|
||||
<EuiSpacer size="s" />
|
||||
<Section section={s} />
|
||||
</Fragment>
|
||||
))}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ width: '50%' }}>
|
||||
{sections
|
||||
.filter(s => s.position === 'right')
|
||||
.map(s => (
|
||||
<Fragment key={s.title}>
|
||||
<EuiSpacer size="s" />
|
||||
<Section section={s} />
|
||||
</Fragment>
|
||||
))}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<div data-test-subj="transformDetailsTabContent">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem style={{ width: '50%' }}>
|
||||
{sections
|
||||
.filter(s => s.position === 'left')
|
||||
.map(s => (
|
||||
<Fragment key={s.title}>
|
||||
<EuiSpacer size="s" />
|
||||
<Section section={s} />
|
||||
</Fragment>
|
||||
))}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem style={{ width: '50%' }}>
|
||||
{sections
|
||||
.filter(s => s.position === 'right')
|
||||
.map(s => (
|
||||
<Fragment key={s.title}>
|
||||
<EuiSpacer size="s" />
|
||||
<Section section={s} />
|
||||
</Fragment>
|
||||
))}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -20,18 +20,20 @@ interface Props {
|
|||
|
||||
export const ExpandedRowJsonPane: FC<Props> = ({ json }) => {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCodeEditor
|
||||
value={JSON.stringify(json, null, 2)}
|
||||
readOnly={true}
|
||||
mode="json"
|
||||
style={{ width: '100%' }}
|
||||
theme="textmate"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}> </EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<div data-test-subj="transformJsonTabContent">
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCodeEditor
|
||||
value={JSON.stringify(json, null, 2)}
|
||||
readOnly={true}
|
||||
mode="json"
|
||||
style={{ width: '100%' }}
|
||||
theme="textmate"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}> </EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { EuiSpacer, EuiBasicTable } from '@elastic/eui';
|
||||
// @ts-ignore
|
||||
|
@ -143,7 +143,7 @@ export const ExpandedRowMessagesPane: React.FC<Props> = ({ transformId }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div data-test-subj="transformMessagesTabContent">
|
||||
<EuiSpacer size="s" />
|
||||
<EuiBasicTable
|
||||
className="transform__TransformTable__messagesPaneTable"
|
||||
|
@ -155,6 +155,6 @@ export const ExpandedRowMessagesPane: React.FC<Props> = ({ transformId }) => {
|
|||
pagination={pagination}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Fragment>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,12 +14,13 @@ import { useApi } from '../../../../hooks/use_api';
|
|||
import {
|
||||
getFlattenedFields,
|
||||
useRefreshTransformList,
|
||||
EsDoc,
|
||||
PreviewRequestBody,
|
||||
TransformPivotConfig,
|
||||
} from '../../../../common';
|
||||
import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public';
|
||||
import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/utils/date_utils';
|
||||
import { TransformTable } from './transform_table';
|
||||
import { transformTableFactory } from './transform_table';
|
||||
|
||||
interface Props {
|
||||
transformConfig: TransformPivotConfig;
|
||||
|
@ -45,12 +46,14 @@ function getDataFromTransform(
|
|||
transformConfig: TransformPivotConfig
|
||||
): { previewRequest: PreviewRequestBody; groupByArr: string[] | [] } {
|
||||
const index = transformConfig.source.index;
|
||||
const query = transformConfig.source.query;
|
||||
const pivot = transformConfig.pivot;
|
||||
const groupByArr = [];
|
||||
|
||||
const previewRequest: PreviewRequestBody = {
|
||||
source: {
|
||||
index,
|
||||
query,
|
||||
},
|
||||
pivot,
|
||||
};
|
||||
|
@ -67,8 +70,8 @@ function getDataFromTransform(
|
|||
}
|
||||
|
||||
export const ExpandedRowPreviewPane: FC<Props> = ({ transformConfig }) => {
|
||||
const [previewData, setPreviewData] = useState([]);
|
||||
const [columns, setColumns] = useState<FieldDataColumnType[] | []>([]);
|
||||
const [previewData, setPreviewData] = useState<EsDoc[]>([]);
|
||||
const [columns, setColumns] = useState<Array<FieldDataColumnType<EsDoc>> | []>([]);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [sortField, setSortField] = useState<string>('');
|
||||
|
@ -97,8 +100,8 @@ export const ExpandedRowPreviewPane: FC<Props> = ({ transformConfig }) => {
|
|||
const columnKeys = getFlattenedFields(resp.preview[0]);
|
||||
columnKeys.sort(sortColumns(groupByArr));
|
||||
|
||||
const tableColumns: FieldDataColumnType[] = columnKeys.map(k => {
|
||||
const column: FieldDataColumnType = {
|
||||
const tableColumns: Array<FieldDataColumnType<EsDoc>> = columnKeys.map(k => {
|
||||
const column: FieldDataColumnType<EsDoc> = {
|
||||
field: k,
|
||||
name: k,
|
||||
sortable: true,
|
||||
|
@ -191,17 +194,27 @@ export const ExpandedRowPreviewPane: FC<Props> = ({ transformConfig }) => {
|
|||
setSortDirection(direction);
|
||||
};
|
||||
|
||||
const transformTableLoading = previewData.length === 0 && isLoading === true;
|
||||
const dataTestSubj = `transformPreviewTabContent${!transformTableLoading ? ' loaded' : ''}`;
|
||||
|
||||
const TransformTable = transformTableFactory<EsDoc>();
|
||||
|
||||
return (
|
||||
<TransformTable
|
||||
allowNeutralSort={false}
|
||||
loading={previewData.length === 0 && isLoading === true}
|
||||
compressed
|
||||
items={previewData}
|
||||
columns={columns}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
error={errorMessage}
|
||||
/>
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<TransformTable
|
||||
allowNeutralSort={false}
|
||||
loading={transformTableLoading}
|
||||
compressed
|
||||
items={previewData}
|
||||
columns={columns}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
rowProps={() => ({
|
||||
'data-test-subj': 'transformPreviewTabContentRow',
|
||||
})}
|
||||
sorting={sorting}
|
||||
error={errorMessage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -42,7 +42,7 @@ import { StopAction } from './action_stop';
|
|||
import { ItemIdToExpandedRowMap, Query, Clause } from './common';
|
||||
import { getColumns } from './columns';
|
||||
import { ExpandedRow } from './expanded_row';
|
||||
import { ProgressBar, TransformTable } from './transform_table';
|
||||
import { ProgressBar, transformTableFactory } from './transform_table';
|
||||
|
||||
function getItemIdToExpandedRowMap(
|
||||
itemIds: TransformId[],
|
||||
|
@ -374,6 +374,8 @@ export const TransformList: FC<Props> = ({
|
|||
onSelectionChange: (selected: TransformListRow[]) => setTransformSelection(selected),
|
||||
};
|
||||
|
||||
const TransformTable = transformTableFactory<TransformListRow>();
|
||||
|
||||
return (
|
||||
<div data-test-subj="transformListTableContainer">
|
||||
<ProgressBar isLoading={isLoading || transformsLoading} />
|
||||
|
|
|
@ -11,7 +11,7 @@ import React, { Fragment } from 'react';
|
|||
|
||||
import { EuiProgress } from '@elastic/eui';
|
||||
|
||||
import { MlInMemoryTableBasic } from '../../../../../shared_imports';
|
||||
import { mlInMemoryTableBasicFactory } from '../../../../../shared_imports';
|
||||
|
||||
// The built in loading progress bar of EuiInMemoryTable causes a full DOM replacement
|
||||
// of the table and doesn't play well with auto-refreshing. That's why we're displaying
|
||||
|
@ -73,32 +73,35 @@ const getInitialSorting = (columns: any, sorting: any) => {
|
|||
};
|
||||
};
|
||||
|
||||
export class TransformTable extends MlInMemoryTableBasic {
|
||||
static getDerivedStateFromProps(nextProps: any, prevState: any) {
|
||||
const derivedState = {
|
||||
...prevState.prevProps,
|
||||
pageIndex: nextProps.pagination.initialPageIndex,
|
||||
pageSize: nextProps.pagination.initialPageSize,
|
||||
};
|
||||
export function transformTableFactory<T>() {
|
||||
const MlInMemoryTableBasic = mlInMemoryTableBasicFactory<T>();
|
||||
return class TransformTable extends MlInMemoryTableBasic {
|
||||
static getDerivedStateFromProps(nextProps: any, prevState: any) {
|
||||
const derivedState = {
|
||||
...prevState.prevProps,
|
||||
pageIndex: nextProps.pagination.initialPageIndex,
|
||||
pageSize: nextProps.pagination.initialPageSize,
|
||||
};
|
||||
|
||||
if (nextProps.items !== prevState.prevProps.items) {
|
||||
Object.assign(derivedState, {
|
||||
prevProps: {
|
||||
items: nextProps.items,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (nextProps.items !== prevState.prevProps.items) {
|
||||
Object.assign(derivedState, {
|
||||
prevProps: {
|
||||
items: nextProps.items,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { sortName, sortDirection } = getInitialSorting(nextProps.columns, nextProps.sorting);
|
||||
if (
|
||||
sortName !== prevState.prevProps.sortName ||
|
||||
sortDirection !== prevState.prevProps.sortDirection
|
||||
) {
|
||||
Object.assign(derivedState, {
|
||||
sortName,
|
||||
sortDirection,
|
||||
});
|
||||
const { sortName, sortDirection } = getInitialSorting(nextProps.columns, nextProps.sorting);
|
||||
if (
|
||||
sortName !== prevState.prevProps.sortName ||
|
||||
sortDirection !== prevState.prevProps.sortDirection
|
||||
) {
|
||||
Object.assign(derivedState, {
|
||||
sortName,
|
||||
sortDirection,
|
||||
});
|
||||
}
|
||||
return derivedState;
|
||||
}
|
||||
return derivedState;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ export {
|
|||
ExpanderColumnType,
|
||||
FieldDataColumnType,
|
||||
ColumnType,
|
||||
MlInMemoryTableBasic,
|
||||
mlInMemoryTableBasicFactory,
|
||||
OnTableChangeArg,
|
||||
SortingPropType,
|
||||
SortDirection,
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
const esArchiver = getService('esArchiver');
|
||||
const transform = getService('transform');
|
||||
|
||||
describe('creation', function() {
|
||||
describe('creation_index_pattern', function() {
|
||||
this.tags(['smoke']);
|
||||
before(async () => {
|
||||
await esArchiver.load('ml/ecommerce');
|
||||
|
@ -56,11 +56,19 @@ export default function({ getService }: FtrProviderContext) {
|
|||
return `dest_${this.transformId}`;
|
||||
},
|
||||
expected: {
|
||||
pivotPreview: {
|
||||
column: 0,
|
||||
values: [`Men's Accessories`],
|
||||
},
|
||||
row: {
|
||||
status: 'stopped',
|
||||
mode: 'batch',
|
||||
progress: '100',
|
||||
},
|
||||
sourcePreview: {
|
||||
columns: 6,
|
||||
rows: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -96,6 +104,13 @@ export default function({ getService }: FtrProviderContext) {
|
|||
await transform.wizard.assertSourceIndexPreviewLoaded();
|
||||
});
|
||||
|
||||
it('shows the source index preview', async () => {
|
||||
await transform.wizard.assertSourceIndexPreview(
|
||||
testData.expected.sourcePreview.columns,
|
||||
testData.expected.sourcePreview.rows
|
||||
);
|
||||
});
|
||||
|
||||
it('displays an empty pivot preview', async () => {
|
||||
await transform.wizard.assertPivotPreviewEmpty();
|
||||
});
|
||||
|
@ -140,6 +155,13 @@ export default function({ getService }: FtrProviderContext) {
|
|||
await transform.wizard.assertPivotPreviewLoaded();
|
||||
});
|
||||
|
||||
it('shows the pivot preview', async () => {
|
||||
await transform.wizard.assertPivotPreviewColumnValues(
|
||||
testData.expected.pivotPreview.column,
|
||||
testData.expected.pivotPreview.values
|
||||
);
|
||||
});
|
||||
|
||||
it('loads the details step', async () => {
|
||||
await transform.wizard.advanceToDetailsStep();
|
||||
});
|
256
x-pack/test/functional/apps/transform/creation_saved_search.ts
Normal file
256
x-pack/test/functional/apps/transform/creation_saved_search.ts
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
interface GroupByEntry {
|
||||
identifier: string;
|
||||
label: string;
|
||||
intervalLabel?: string;
|
||||
}
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const transform = getService('transform');
|
||||
|
||||
describe('creation_saved_search', function() {
|
||||
this.tags(['smoke']);
|
||||
before(async () => {
|
||||
await esArchiver.load('ml/farequote');
|
||||
});
|
||||
|
||||
// after(async () => {
|
||||
// await esArchiver.unload('ml/farequote');
|
||||
// await transform.api.cleanTransformIndices();
|
||||
// });
|
||||
|
||||
const testDataList = [
|
||||
{
|
||||
suiteTitle: 'batch transform with terms groups and avg agg with saved search filter',
|
||||
source: 'farequote_filter',
|
||||
groupByEntries: [
|
||||
{
|
||||
identifier: 'terms(airline)',
|
||||
label: 'airline',
|
||||
} as GroupByEntry,
|
||||
],
|
||||
aggregationEntries: [
|
||||
{
|
||||
identifier: 'avg(responsetime)',
|
||||
label: 'responsetime.avg',
|
||||
},
|
||||
],
|
||||
transformId: `fq_1_${Date.now()}`,
|
||||
transformDescription:
|
||||
'farequote batch transform with groups terms(airline) and aggregation avg(responsetime.avg) with saved search filter',
|
||||
get destinationIndex(): string {
|
||||
return `dest_${this.transformId}`;
|
||||
},
|
||||
expected: {
|
||||
pivotPreview: {
|
||||
column: 0,
|
||||
values: ['ASA'],
|
||||
},
|
||||
row: {
|
||||
status: 'stopped',
|
||||
mode: 'batch',
|
||||
progress: '100',
|
||||
},
|
||||
sourceIndex: 'farequote',
|
||||
sourcePreview: {
|
||||
column: 3,
|
||||
values: ['ASA'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const testData of testDataList) {
|
||||
describe(`${testData.suiteTitle}`, function() {
|
||||
after(async () => {
|
||||
await transform.api.deleteIndices(testData.destinationIndex);
|
||||
});
|
||||
|
||||
it('loads the home page', async () => {
|
||||
await transform.navigation.navigateTo();
|
||||
await transform.management.assertTransformListPageExists();
|
||||
});
|
||||
|
||||
it('displays the stats bar', async () => {
|
||||
await transform.management.assertTransformStatsBarExists();
|
||||
});
|
||||
|
||||
it('loads the source selection modal', async () => {
|
||||
await transform.management.startTransformCreation();
|
||||
});
|
||||
|
||||
it('selects the source data', async () => {
|
||||
await transform.sourceSelection.selectSource(testData.source);
|
||||
});
|
||||
|
||||
it('displays the define pivot step', async () => {
|
||||
await transform.wizard.assertDefineStepActive();
|
||||
});
|
||||
|
||||
it('loads the source index preview', async () => {
|
||||
await transform.wizard.assertSourceIndexPreviewLoaded();
|
||||
});
|
||||
|
||||
it('shows the filtered source index preview', async () => {
|
||||
await transform.wizard.assertSourceIndexPreviewColumnValues(
|
||||
testData.expected.sourcePreview.column,
|
||||
testData.expected.sourcePreview.values
|
||||
);
|
||||
});
|
||||
|
||||
it('displays an empty pivot preview', async () => {
|
||||
await transform.wizard.assertPivotPreviewEmpty();
|
||||
});
|
||||
|
||||
it('hides the query input', async () => {
|
||||
await transform.wizard.assertQueryInputMissing();
|
||||
});
|
||||
|
||||
it('hides the advanced query editor switch', async () => {
|
||||
await transform.wizard.assertAdvancedQueryEditorSwitchMissing();
|
||||
});
|
||||
|
||||
it('adds the group by entries', async () => {
|
||||
for (const [index, entry] of testData.groupByEntries.entries()) {
|
||||
await transform.wizard.assertGroupByInputExists();
|
||||
await transform.wizard.assertGroupByInputValue([]);
|
||||
await transform.wizard.addGroupByEntry(
|
||||
index,
|
||||
entry.identifier,
|
||||
entry.label,
|
||||
entry.intervalLabel
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('adds the aggregation entries', async () => {
|
||||
for (const [index, agg] of testData.aggregationEntries.entries()) {
|
||||
await transform.wizard.assertAggregationInputExists();
|
||||
await transform.wizard.assertAggregationInputValue([]);
|
||||
await transform.wizard.addAggregationEntry(index, agg.identifier, agg.label);
|
||||
}
|
||||
});
|
||||
|
||||
it('displays the advanced pivot editor switch', async () => {
|
||||
await transform.wizard.assertAdvancedPivotEditorSwitchExists();
|
||||
await transform.wizard.assertAdvancedPivotEditorSwitchCheckState(false);
|
||||
});
|
||||
|
||||
it('loads the pivot preview', async () => {
|
||||
await transform.wizard.assertPivotPreviewLoaded();
|
||||
});
|
||||
|
||||
it('shows the pivot preview', async () => {
|
||||
await transform.wizard.assertPivotPreviewColumnValues(
|
||||
testData.expected.pivotPreview.column,
|
||||
testData.expected.pivotPreview.values
|
||||
);
|
||||
});
|
||||
|
||||
it('loads the details step', async () => {
|
||||
await transform.wizard.advanceToDetailsStep();
|
||||
});
|
||||
|
||||
it('inputs the transform id', async () => {
|
||||
await transform.wizard.assertTransformIdInputExists();
|
||||
await transform.wizard.assertTransformIdValue('');
|
||||
await transform.wizard.setTransformId(testData.transformId);
|
||||
});
|
||||
|
||||
it('inputs the transform description', async () => {
|
||||
await transform.wizard.assertTransformDescriptionInputExists();
|
||||
await transform.wizard.assertTransformDescriptionValue('');
|
||||
await transform.wizard.setTransformDescription(testData.transformDescription);
|
||||
});
|
||||
|
||||
it('inputs the destination index', async () => {
|
||||
await transform.wizard.assertDestinationIndexInputExists();
|
||||
await transform.wizard.assertDestinationIndexValue('');
|
||||
await transform.wizard.setDestinationIndex(testData.destinationIndex);
|
||||
});
|
||||
|
||||
it('displays the create index pattern switch', async () => {
|
||||
await transform.wizard.assertCreateIndexPatternSwitchExists();
|
||||
await transform.wizard.assertCreateIndexPatternSwitchCheckState(true);
|
||||
});
|
||||
|
||||
it('displays the continuous mode switch', async () => {
|
||||
await transform.wizard.assertContinuousModeSwitchExists();
|
||||
await transform.wizard.assertContinuousModeSwitchCheckState(false);
|
||||
});
|
||||
|
||||
it('loads the create step', async () => {
|
||||
await transform.wizard.advanceToCreateStep();
|
||||
});
|
||||
|
||||
it('displays the create and start button', async () => {
|
||||
await transform.wizard.assertCreateAndStartButtonExists();
|
||||
});
|
||||
|
||||
it('displays the create button', async () => {
|
||||
await transform.wizard.assertCreateButtonExists();
|
||||
});
|
||||
|
||||
it('displays the copy to clipboard button', async () => {
|
||||
await transform.wizard.assertCreateAndStartButtonExists();
|
||||
});
|
||||
|
||||
it('creates the transform', async () => {
|
||||
await transform.wizard.createTransform();
|
||||
});
|
||||
|
||||
it('starts the transform and finishes processing', async () => {
|
||||
await transform.wizard.startTransform();
|
||||
await transform.wizard.waitForProgressBarComplete();
|
||||
});
|
||||
|
||||
it('returns to the management page', async () => {
|
||||
await transform.wizard.returnToManagement();
|
||||
});
|
||||
|
||||
it('displays the transforms table', async () => {
|
||||
await transform.management.assertTransformsTableExists();
|
||||
});
|
||||
|
||||
it('displays the created transform in the transform list', async () => {
|
||||
await transform.table.refreshTransformList();
|
||||
await transform.table.filterWithSearchString(testData.transformId);
|
||||
const rows = await transform.table.parseTransformTable();
|
||||
expect(rows.filter(row => row.id === testData.transformId)).to.have.length(1);
|
||||
});
|
||||
|
||||
it('job creation displays details for the created job in the job list', async () => {
|
||||
await transform.table.assertTransformRowFields(testData.transformId, {
|
||||
id: testData.transformId,
|
||||
description: testData.transformDescription,
|
||||
sourceIndex: testData.expected.sourceIndex,
|
||||
destinationIndex: testData.destinationIndex,
|
||||
status: testData.expected.row.status,
|
||||
mode: testData.expected.row.mode,
|
||||
progress: testData.expected.row.progress,
|
||||
});
|
||||
});
|
||||
|
||||
it('expands the transform management table row and walks through available tabs', async () => {
|
||||
await transform.table.assertTransformExpandedRow();
|
||||
});
|
||||
|
||||
it('displays the transform preview in the expanded row', async () => {
|
||||
await transform.table.assertTransformsExpandedRowPreviewColumnValues(
|
||||
testData.expected.pivotPreview.column,
|
||||
testData.expected.pivotPreview.values
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -9,6 +9,7 @@ export default function({ loadTestFile }: FtrProviderContext) {
|
|||
describe('transform', function() {
|
||||
this.tags(['ciGroup9', 'transform']);
|
||||
|
||||
loadTestFile(require.resolve('./creation'));
|
||||
loadTestFile(require.resolve('./creation_index_pattern'));
|
||||
loadTestFile(require.resolve('./creation_saved_search'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import expect from '@kbn/expect';
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export function TransformTableProvider({ getService }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return new (class TransformTable {
|
||||
|
@ -60,6 +61,51 @@ export function TransformTableProvider({ getService }: FtrProviderContext) {
|
|||
return rows;
|
||||
}
|
||||
|
||||
async parseEuiInMemoryTable(tableSubj: string) {
|
||||
const table = await testSubjects.find(`~${tableSubj}`);
|
||||
const $ = await table.parseDomContent();
|
||||
const rows = [];
|
||||
|
||||
// For each row, get the content of each cell and
|
||||
// add its values as an array to each row.
|
||||
for (const tr of $.findTestSubjects(`~${tableSubj}Row`).toArray()) {
|
||||
rows.push(
|
||||
$(tr)
|
||||
.find('.euiTableCellContent')
|
||||
.toArray()
|
||||
.map(cell =>
|
||||
$(cell)
|
||||
.text()
|
||||
.trim()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async assertEuiInMemoryTableColumnValues(
|
||||
tableSubj: string,
|
||||
column: number,
|
||||
expectedColumnValues: string[]
|
||||
) {
|
||||
await retry.tryForTime(2000, async () => {
|
||||
// get a 2D array of rows and cell values
|
||||
const rows = await this.parseEuiInMemoryTable(tableSubj);
|
||||
|
||||
// reduce the rows data to an array of unique values in the specified column
|
||||
const uniqueColumnValues = rows
|
||||
.map(row => row[column])
|
||||
.flat()
|
||||
.filter((v, i, a) => a.indexOf(v) === i);
|
||||
|
||||
uniqueColumnValues.sort();
|
||||
|
||||
// check if the returned unique value matches the supplied filter value
|
||||
expect(uniqueColumnValues).to.eql(expectedColumnValues);
|
||||
});
|
||||
}
|
||||
|
||||
public async refreshTransformList() {
|
||||
await testSubjects.click('transformRefreshTransformListButton');
|
||||
await this.waitForTransformsToLoad();
|
||||
|
@ -83,5 +129,36 @@ export function TransformTableProvider({ getService }: FtrProviderContext) {
|
|||
const transformRow = rows.filter(row => row.id === transformId)[0];
|
||||
expect(transformRow).to.eql(expectedRow);
|
||||
}
|
||||
|
||||
public async assertTransformExpandedRow() {
|
||||
await testSubjects.click('transformListRowDetailsToggle');
|
||||
|
||||
// The expanded row should show the details tab content by default
|
||||
await testSubjects.existOrFail('transformDetailsTab');
|
||||
await testSubjects.existOrFail('~transformDetailsTabContent');
|
||||
|
||||
// Walk through the rest of the tabs and check if the corresponding content shows up
|
||||
await testSubjects.existOrFail('transformJsonTab');
|
||||
await testSubjects.click('transformJsonTab');
|
||||
await testSubjects.existOrFail('~transformJsonTabContent');
|
||||
|
||||
await testSubjects.existOrFail('transformMessagesTab');
|
||||
await testSubjects.click('transformMessagesTab');
|
||||
await testSubjects.existOrFail('~transformMessagesTabContent');
|
||||
|
||||
await testSubjects.existOrFail('transformPreviewTab');
|
||||
await testSubjects.click('transformPreviewTab');
|
||||
await testSubjects.existOrFail('~transformPreviewTabContent');
|
||||
}
|
||||
|
||||
public async waitForTransformsExpandedRowPreviewTabToLoad() {
|
||||
await testSubjects.existOrFail('~transformPreviewTabContent', { timeout: 60 * 1000 });
|
||||
await testSubjects.existOrFail('transformPreviewTabContent loaded', { timeout: 30 * 1000 });
|
||||
}
|
||||
|
||||
async assertTransformsExpandedRowPreviewColumnValues(column: number, values: string[]) {
|
||||
await this.waitForTransformsExpandedRowPreviewTabToLoad();
|
||||
await this.assertEuiInMemoryTableColumnValues('transformPreviewTabContent', column, values);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
|
@ -75,6 +75,81 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) {
|
|||
await testSubjects.existOrFail(selector);
|
||||
},
|
||||
|
||||
async parseEuiInMemoryTable(tableSubj: string) {
|
||||
const table = await testSubjects.find(`~${tableSubj}`);
|
||||
const $ = await table.parseDomContent();
|
||||
const rows = [];
|
||||
|
||||
// For each row, get the content of each cell and
|
||||
// add its values as an array to each row.
|
||||
for (const tr of $.findTestSubjects(`~${tableSubj}Row`).toArray()) {
|
||||
rows.push(
|
||||
$(tr)
|
||||
.find('.euiTableCellContent')
|
||||
.toArray()
|
||||
.map(cell =>
|
||||
$(cell)
|
||||
.text()
|
||||
.trim()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return rows;
|
||||
},
|
||||
|
||||
async assertEuiInMemoryTableColumnValues(
|
||||
tableSubj: string,
|
||||
column: number,
|
||||
expectedColumnValues: string[]
|
||||
) {
|
||||
await retry.tryForTime(2000, async () => {
|
||||
// get a 2D array of rows and cell values
|
||||
const rows = await this.parseEuiInMemoryTable(tableSubj);
|
||||
|
||||
// reduce the rows data to an array of unique values in the specified column
|
||||
const uniqueColumnValues = rows
|
||||
.map(row => row[column])
|
||||
.flat()
|
||||
.filter((v, i, a) => a.indexOf(v) === i);
|
||||
|
||||
uniqueColumnValues.sort();
|
||||
|
||||
// check if the returned unique value matches the supplied filter value
|
||||
expect(uniqueColumnValues).to.eql(
|
||||
expectedColumnValues,
|
||||
`Unique EuiInMemoryTable column values should be '${expectedColumnValues.join()}' (got ${uniqueColumnValues.join()})`
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
async assertSourceIndexPreview(columns: number, rows: number) {
|
||||
await retry.tryForTime(2000, async () => {
|
||||
// get a 2D array of rows and cell values
|
||||
const rowsData = await this.parseEuiInMemoryTable('transformSourceIndexPreview');
|
||||
|
||||
expect(rowsData).to.length(
|
||||
rows,
|
||||
`EuiInMemoryTable rows should be ${rows} (got ${rowsData.length})`
|
||||
);
|
||||
|
||||
rowsData.map((r, i) =>
|
||||
expect(r).to.length(
|
||||
columns,
|
||||
`EuiInMemoryTable row #${i + 1} column count should be ${columns} (got ${r.length})`
|
||||
)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
async assertSourceIndexPreviewColumnValues(column: number, values: string[]) {
|
||||
await this.assertEuiInMemoryTableColumnValues('transformSourceIndexPreview', column, values);
|
||||
},
|
||||
|
||||
async assertPivotPreviewColumnValues(column: number, values: string[]) {
|
||||
await this.assertEuiInMemoryTableColumnValues('transformPivotPreview', column, values);
|
||||
},
|
||||
|
||||
async assertPivotPreviewLoaded() {
|
||||
await this.assertPivotPreviewExists('loaded');
|
||||
},
|
||||
|
@ -87,6 +162,10 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) {
|
|||
await testSubjects.existOrFail('tarnsformQueryInput');
|
||||
},
|
||||
|
||||
async assertQueryInputMissing() {
|
||||
await testSubjects.missingOrFail('tarnsformQueryInput');
|
||||
},
|
||||
|
||||
async assertQueryValue(expectedQuery: string) {
|
||||
const actualQuery = await testSubjects.getVisibleText('tarnsformQueryInput');
|
||||
expect(actualQuery).to.eql(
|
||||
|
@ -99,6 +178,10 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) {
|
|||
await testSubjects.existOrFail(`transformAdvancedQueryEditorSwitch`, { allowHidden: true });
|
||||
},
|
||||
|
||||
async assertAdvancedQueryEditorSwitchMissing() {
|
||||
await testSubjects.missingOrFail(`transformAdvancedQueryEditorSwitch`);
|
||||
},
|
||||
|
||||
async assertAdvancedQueryEditorSwitchCheckState(expectedCheckState: boolean) {
|
||||
const actualCheckState =
|
||||
(await testSubjects.getAttribute('transformAdvancedQueryEditorSwitch', 'aria-checked')) ===
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue