[ML] Transforms: Replace KqlFilterBar with QueryStringInput. (#59723)

- Replaces the custom KqlFilterBar with Kibana's QueryStringInput. This means the wizard now supports both lucene and kuery input.
- Using this component we no longer need to do cross-imports from the ML plugin. The use of setDependencyCache is no longer necessary.
- Replaces the custom AppDependencies provider code with Kibana's KibanaContextProvider.
This commit is contained in:
Walter Rafelsberger 2020-03-12 15:55:41 +01:00 committed by GitHub
parent ca67b80460
commit 1ede10ccbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 212 additions and 252 deletions

View file

@ -4,11 +4,8 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
jest.mock('ui/new_platform');
export const expandLiteralStrings = jest.fn(); export const expandLiteralStrings = jest.fn();
export const XJsonMode = jest.fn(); export const XJsonMode = jest.fn();
export const setDependencyCache = jest.fn();
export const useRequest = jest.fn(() => ({ export const useRequest = jest.fn(() => ({
isLoading: false, isLoading: false,
error: null, error: null,
@ -16,4 +13,3 @@ export const useRequest = jest.fn(() => ({
})); }));
export { mlInMemoryTableBasicFactory } from '../../../../legacy/plugins/ml/public/application/components/ml_in_memory_table'; export { mlInMemoryTableBasicFactory } from '../../../../legacy/plugins/ml/public/application/components/ml_in_memory_table';
export const SORT_DIRECTION = { ASC: 'asc' }; export const SORT_DIRECTION = { ASC: 'asc' };
export const KqlFilterBar = jest.fn(() => null);

View file

@ -4,25 +4,31 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { coreMock } from '../../../../../src/core/public/mocks'; import { coreMock } from '../../../../../../src/core/public/mocks';
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { getAppProviders, AppDependencies } from './app_dependencies';
const coreSetup = coreMock.createSetup(); const coreSetup = coreMock.createSetup();
const coreStart = coreMock.createStart(); const coreStart = coreMock.createStart();
const dataStart = dataPluginMock.createStartContract(); const dataStart = dataPluginMock.createStartContract();
const appDependencies: AppDependencies = { const appDependencies = {
chrome: coreStart.chrome, chrome: coreStart.chrome,
data: dataStart, data: dataStart,
docLinks: coreStart.docLinks, docLinks: coreStart.docLinks,
i18n: coreStart.i18n, i18n: coreStart.i18n,
notifications: coreStart.notifications, notifications: coreSetup.notifications,
uiSettings: coreStart.uiSettings, uiSettings: coreStart.uiSettings,
savedObjects: coreStart.savedObjects, savedObjects: coreStart.savedObjects,
storage: ({ get: jest.fn() } as unknown) as Storage,
overlays: coreStart.overlays, overlays: coreStart.overlays,
http: coreSetup.http, http: coreSetup.http,
}; };
export const Providers = getAppProviders(appDependencies); export const useAppDependencies = () => {
return appDependencies;
};
export const useToastNotifications = () => {
return coreSetup.notifications;
};

View file

@ -10,10 +10,13 @@ import { HashRouter, Redirect, Route, Switch } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { API_BASE_PATH } from '../../common/constants';
import { SectionError } from './components'; import { SectionError } from './components';
import { CLIENT_BASE_PATH, SECTION_SLUG } from './constants'; import { CLIENT_BASE_PATH, SECTION_SLUG } from './constants';
import { getAppProviders } from './app_dependencies'; import { AuthorizationContext, AuthorizationProvider } from './lib/authorization';
import { AuthorizationContext } from './lib/authorization';
import { AppDependencies } from './app_dependencies'; import { AppDependencies } from './app_dependencies';
import { CloneTransformSection } from './sections/clone_transform'; import { CloneTransformSection } from './sections/clone_transform';
@ -61,12 +64,16 @@ export const App: FC = () => {
}; };
export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => { export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => {
const Providers = getAppProviders(appDependencies); const I18nContext = appDependencies.i18n.Context;
render( render(
<Providers> <KibanaContextProvider services={appDependencies}>
<App /> <AuthorizationProvider privilegesEndpoint={`${API_BASE_PATH}privileges`}>
</Providers>, <I18nContext>
<App />
</I18nContext>
</AuthorizationProvider>
</KibanaContextProvider>,
element element
); );

View file

@ -4,17 +4,11 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import React, { createContext, useContext, ReactNode } from 'react';
import { HashRouter } from 'react-router-dom';
import { CoreSetup, CoreStart } from 'src/core/public'; import { CoreSetup, CoreStart } from 'src/core/public';
import { DataPublicPluginStart } from 'src/plugins/data/public'; import { DataPublicPluginStart } from 'src/plugins/data/public';
import { API_BASE_PATH } from '../../common/constants'; import { useKibana } from '../../../../../src/plugins/kibana_react/public';
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
import { setDependencyCache } from '../shared_imports';
import { AuthorizationProvider } from './lib/authorization';
export interface AppDependencies { export interface AppDependencies {
chrome: CoreStart['chrome']; chrome: CoreStart['chrome'];
@ -22,36 +16,15 @@ export interface AppDependencies {
docLinks: CoreStart['docLinks']; docLinks: CoreStart['docLinks'];
http: CoreSetup['http']; http: CoreSetup['http'];
i18n: CoreStart['i18n']; i18n: CoreStart['i18n'];
notifications: CoreStart['notifications']; notifications: CoreSetup['notifications'];
uiSettings: CoreStart['uiSettings']; uiSettings: CoreStart['uiSettings'];
savedObjects: CoreStart['savedObjects']; savedObjects: CoreStart['savedObjects'];
storage: Storage;
overlays: CoreStart['overlays']; overlays: CoreStart['overlays'];
} }
let DependenciesContext: React.Context<AppDependencies>;
const setAppDependencies = (deps: AppDependencies) => {
const legacyBasePath = {
prepend: deps.http.basePath.prepend,
get: deps.http.basePath.get,
remove: () => {},
};
setDependencyCache({
autocomplete: deps.data.autocomplete,
docLinks: deps.docLinks,
basePath: legacyBasePath as any,
});
DependenciesContext = createContext<AppDependencies>(deps);
return DependenciesContext.Provider;
};
export const useAppDependencies = () => { export const useAppDependencies = () => {
if (!DependenciesContext) { return useKibana().services as AppDependencies;
throw new Error(`The app dependencies Context hasn't been set.
Use the "setAppDependencies()" method when bootstrapping the app.`);
}
return useContext<AppDependencies>(DependenciesContext);
}; };
export const useToastNotifications = () => { export const useToastNotifications = () => {
@ -60,20 +33,3 @@ export const useToastNotifications = () => {
} = useAppDependencies(); } = useAppDependencies();
return toastNotifications; return toastNotifications;
}; };
export const getAppProviders = (deps: AppDependencies) => {
const I18nContext = deps.i18n.Context;
// Create App dependencies context and get its provider
const AppDependenciesProvider = setAppDependencies(deps);
return ({ children }: { children: ReactNode }) => (
<AuthorizationProvider privilegesEndpoint={`${API_BASE_PATH}privileges`}>
<I18nContext>
<HashRouter>
<AppDependenciesProvider value={deps}>{children}</AppDependenciesProvider>
</HashRouter>
</I18nContext>
</AuthorizationProvider>
);
};

View file

@ -143,6 +143,7 @@ describe('Transform: Common', () => {
isAdvancedPivotEditorEnabled: false, isAdvancedPivotEditorEnabled: false,
isAdvancedSourceEditorEnabled: false, isAdvancedSourceEditorEnabled: false,
sourceConfigUpdated: false, sourceConfigUpdated: false,
searchLanguage: 'kuery',
searchString: 'the-query', searchString: 'the-query',
searchQuery: 'the-search-query', searchQuery: 'the-search-query',
valid: true, valid: true,

View file

@ -8,7 +8,6 @@ import React from 'react';
import { render, wait } from '@testing-library/react'; import { render, wait } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom/extend-expect';
import { Providers } from '../../app_dependencies.mock';
import { import {
getPivotQuery, getPivotQuery,
PivotAggsConfig, PivotAggsConfig,
@ -19,8 +18,8 @@ import {
import { PivotPreview } from './pivot_preview'; import { PivotPreview } from './pivot_preview';
jest.mock('ui/new_platform');
jest.mock('../../../shared_imports'); jest.mock('../../../shared_imports');
jest.mock('../../../app/app_dependencies');
describe('Transform: <PivotPreview />', () => { describe('Transform: <PivotPreview />', () => {
// Using the async/await wait()/done() pattern to avoid act() errors. // Using the async/await wait()/done() pattern to avoid act() errors.
@ -45,11 +44,7 @@ describe('Transform: <PivotPreview />', () => {
query: getPivotQuery('the-query'), query: getPivotQuery('the-query'),
}; };
const { getByText } = render( const { getByText } = render(<PivotPreview {...props} />);
<Providers>
<PivotPreview {...props} />
</Providers>
);
// Act // Act
// Assert // Assert

View file

@ -7,23 +7,17 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { Providers } from '../app_dependencies.mock';
import { ToastNotificationText } from './toast_notification_text'; import { ToastNotificationText } from './toast_notification_text';
jest.mock('../../shared_imports'); jest.mock('../../shared_imports');
jest.mock('ui/new_platform'); jest.mock('../../app/app_dependencies');
describe('ToastNotificationText', () => { describe('ToastNotificationText', () => {
test('should render the text as plain text', () => { test('should render the text as plain text', () => {
const props = { const props = {
text: 'a short text message', text: 'a short text message',
}; };
const { container } = render( const { container } = render(<ToastNotificationText {...props} />);
<Providers>
<ToastNotificationText {...props} />
</Providers>
);
expect(container.textContent).toBe('a short text message'); expect(container.textContent).toBe('a short text message');
}); });
@ -32,11 +26,7 @@ describe('ToastNotificationText', () => {
text: text:
'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 characters. ', 'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 characters. ',
}; };
const { container } = render( const { container } = render(<ToastNotificationText {...props} />);
<Providers>
<ToastNotificationText {...props} />
</Providers>
);
expect(container.textContent).toBe( expect(container.textContent).toBe(
'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 ...View details' 'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 ...View details'
); );

View file

@ -8,14 +8,13 @@ import React from 'react';
import { render, wait } from '@testing-library/react'; import { render, wait } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom/extend-expect';
import { Providers } from '../../../../app_dependencies.mock';
import { getPivotQuery } from '../../../../common'; import { getPivotQuery } from '../../../../common';
import { SearchItems } from '../../../../hooks/use_search_items'; import { SearchItems } from '../../../../hooks/use_search_items';
import { SourceIndexPreview } from './source_index_preview'; import { SourceIndexPreview } from './source_index_preview';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports'); jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
describe('Transform: <SourceIndexPreview />', () => { describe('Transform: <SourceIndexPreview />', () => {
// Using the async/await wait()/done() pattern to avoid act() errors. // Using the async/await wait()/done() pattern to avoid act() errors.
@ -28,11 +27,7 @@ describe('Transform: <SourceIndexPreview />', () => {
} as SearchItems['indexPattern'], } as SearchItems['indexPattern'],
query: getPivotQuery('the-query'), query: getPivotQuery('the-query'),
}; };
const { getByText } = render( const { getByText } = render(<SourceIndexPreview {...props} />);
<Providers>
<SourceIndexPreview {...props} />
</Providers>
);
// Act // Act
// Assert // Assert

View file

@ -8,12 +8,10 @@ import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom/extend-expect';
import { Providers } from '../../../../app_dependencies.mock';
import { StepCreateForm } from './step_create_form'; import { StepCreateForm } from './step_create_form';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports'); jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
describe('Transform: <StepCreateForm />', () => { describe('Transform: <StepCreateForm />', () => {
test('Minimal initialization', () => { test('Minimal initialization', () => {
@ -26,11 +24,7 @@ describe('Transform: <StepCreateForm />', () => {
onChange() {}, onChange() {},
}; };
const { getByText } = render( const { getByText } = render(<StepCreateForm {...props} />);
<Providers>
<StepCreateForm {...props} />
</Providers>
);
// Act // Act
// Assert // Assert

View file

@ -8,7 +8,14 @@ import React from 'react';
import { render, wait } from '@testing-library/react'; import { render, wait } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom/extend-expect';
import { Providers } from '../../../../app_dependencies.mock'; import { I18nProvider } from '@kbn/i18n/react';
import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public';
import { coreMock } from '../../../../../../../../../src/core/public/mocks';
import { dataPluginMock } from '../../../../../../../../../src/plugins/data/public/mocks';
const startMock = coreMock.createStart();
import { import {
PivotAggsConfigDict, PivotAggsConfigDict,
PivotGroupByConfigDict, PivotGroupByConfigDict,
@ -19,8 +26,25 @@ import { SearchItems } from '../../../../hooks/use_search_items';
import { StepDefineForm, getAggNameConflictToastMessages } from './step_define_form'; import { StepDefineForm, getAggNameConflictToastMessages } from './step_define_form';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports'); jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
const createMockWebStorage = () => ({
clear: jest.fn(),
getItem: jest.fn(),
key: jest.fn(),
removeItem: jest.fn(),
setItem: jest.fn(),
length: 0,
});
const createMockStorage = () => ({
storage: createMockWebStorage(),
get: jest.fn(),
set: jest.fn(),
remove: jest.fn(),
clear: jest.fn(),
});
describe('Transform: <DefinePivotForm />', () => { describe('Transform: <DefinePivotForm />', () => {
// Using the async/await wait()/done() pattern to avoid act() errors. // Using the async/await wait()/done() pattern to avoid act() errors.
@ -32,10 +56,21 @@ describe('Transform: <DefinePivotForm />', () => {
fields: [] as any[], fields: [] as any[],
} as SearchItems['indexPattern'], } as SearchItems['indexPattern'],
}; };
// mock services for QueryStringInput
const services = {
...startMock,
data: dataPluginMock.createStartContract(),
appName: 'the-test-app',
storage: createMockStorage(),
};
const { getByLabelText } = render( const { getByLabelText } = render(
<Providers> <I18nProvider>
<StepDefineForm onChange={jest.fn()} searchItems={searchItems as SearchItems} /> <KibanaContextProvider services={services}>
</Providers> <StepDefineForm onChange={jest.fn()} searchItems={searchItems as SearchItems} />
</KibanaContextProvider>
</I18nProvider>
); );
// Act // Act

View file

@ -20,13 +20,19 @@ import {
EuiHorizontalRule, EuiHorizontalRule,
EuiLink, EuiLink,
EuiPanel, EuiPanel,
// @ts-ignore
EuiSearchBar,
EuiSpacer, EuiSpacer,
EuiSwitch, EuiSwitch,
} from '@elastic/eui'; } from '@elastic/eui';
import {
esKuery,
esQuery,
Query,
QueryStringInput,
} from '../../../../../../../../../src/plugins/data/public';
import { PivotPreview } from '../../../../components/pivot_preview'; import { PivotPreview } from '../../../../components/pivot_preview';
import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; import { useDocumentationLinks } from '../../../../hooks/use_documentation_links';
import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items'; import { SavedSearchQuery, SearchItems } from '../../../../hooks/use_search_items';
import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode'; import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode';
@ -37,13 +43,11 @@ import { DropDown } from '../aggregation_dropdown';
import { AggListForm } from '../aggregation_list'; import { AggListForm } from '../aggregation_list';
import { GroupByListForm } from '../group_by_list'; import { GroupByListForm } from '../group_by_list';
import { SourceIndexPreview } from '../source_index_preview'; import { SourceIndexPreview } from '../source_index_preview';
import { KqlFilterBar } from '../../../../../shared_imports';
import { SwitchModal } from './switch_modal'; import { SwitchModal } from './switch_modal';
import { import {
getPivotQuery, getPivotQuery,
getPreviewRequestBody, getPreviewRequestBody,
isMatchAllQuery,
matchAllQuery, matchAllQuery,
AggName, AggName,
DropDownLabel, DropDownLabel,
@ -65,14 +69,18 @@ export interface StepDefineExposedState {
groupByList: PivotGroupByConfigDict; groupByList: PivotGroupByConfigDict;
isAdvancedPivotEditorEnabled: boolean; isAdvancedPivotEditorEnabled: boolean;
isAdvancedSourceEditorEnabled: boolean; isAdvancedSourceEditorEnabled: boolean;
searchString: string | SavedSearchQuery; searchLanguage: QUERY_LANGUAGE;
searchString: string | undefined;
searchQuery: string | SavedSearchQuery; searchQuery: string | SavedSearchQuery;
sourceConfigUpdated: boolean; sourceConfigUpdated: boolean;
valid: boolean; valid: boolean;
} }
const defaultSearch = '*'; const defaultSearch = '*';
const emptySearch = '';
const QUERY_LANGUAGE_KUERY = 'kuery';
const QUERY_LANGUAGE_LUCENE = 'lucene';
type QUERY_LANGUAGE = 'kuery' | 'lucene';
export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineExposedState { export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineExposedState {
return { return {
@ -80,7 +88,8 @@ export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineE
groupByList: {} as PivotGroupByConfigDict, groupByList: {} as PivotGroupByConfigDict,
isAdvancedPivotEditorEnabled: false, isAdvancedPivotEditorEnabled: false,
isAdvancedSourceEditorEnabled: false, isAdvancedSourceEditorEnabled: false,
searchString: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, searchLanguage: QUERY_LANGUAGE_KUERY,
searchString: undefined,
searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch,
sourceConfigUpdated: false, sourceConfigUpdated: false,
valid: false, valid: false,
@ -126,7 +135,6 @@ export function applyTransformConfigToDefineState(
const query = transformConfig.source.query; const query = transformConfig.source.query;
if (query !== undefined && !isEqual(query, matchAllQuery)) { if (query !== undefined && !isEqual(query, matchAllQuery)) {
state.isAdvancedSourceEditorEnabled = true; state.isAdvancedSourceEditorEnabled = true;
state.searchString = '';
state.searchQuery = query; state.searchQuery = query;
state.sourceConfigUpdated = true; state.sourceConfigUpdated = true;
} }
@ -243,24 +251,45 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
const defaults = { ...getDefaultStepDefineState(searchItems), ...overrides }; const defaults = { ...getDefaultStepDefineState(searchItems), ...overrides };
// The search filter // The internal state of the input query bar updated on every key stroke.
const [searchString, setSearchString] = useState(defaults.searchString); const [searchInput, setSearchInput] = useState<Query>({
const [searchQuery, setSearchQuery] = useState(defaults.searchQuery); query: defaults.searchString || '',
const [useKQL] = useState(true); language: defaults.searchLanguage,
});
const searchHandler = (d: Record<string, any>) => { // The state of the input query bar updated on every submit and to be exposed.
const { filterQuery, queryString } = d; const [searchLanguage, setSearchLanguage] = useState<StepDefineExposedState['searchLanguage']>(
const newSearch = queryString === emptySearch ? defaultSearch : queryString; defaults.searchLanguage
const newSearchQuery = isMatchAllQuery(filterQuery) ? defaultSearch : filterQuery; );
setSearchString(newSearch); const [searchString, setSearchString] = useState<StepDefineExposedState['searchString']>(
setSearchQuery(newSearchQuery); defaults.searchString
);
const [searchQuery, setSearchQuery] = useState(defaults.searchQuery);
const { indexPattern } = searchItems;
const searchChangeHandler = (query: Query) => setSearchInput(query);
const searchSubmitHandler = (query: Query) => {
setSearchLanguage(query.language as QUERY_LANGUAGE);
setSearchString(query.query !== '' ? (query.query as string) : undefined);
switch (query.language) {
case QUERY_LANGUAGE_KUERY:
setSearchQuery(
esKuery.toElasticsearchQuery(
esKuery.fromKueryExpression(query.query as string),
indexPattern
)
);
return;
case QUERY_LANGUAGE_LUCENE:
setSearchQuery(esQuery.luceneStringToDsl(query.query as string));
return;
}
}; };
// The list of selected group by fields // The list of selected group by fields
const [groupByList, setGroupByList] = useState(defaults.groupByList); const [groupByList, setGroupByList] = useState(defaults.groupByList);
const { indexPattern } = searchItems;
const { const {
groupByOptions, groupByOptions,
groupByOptionsData, groupByOptionsData,
@ -349,7 +378,7 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
const pivotAggsArr = dictionaryToArray(aggList); const pivotAggsArr = dictionaryToArray(aggList);
const pivotGroupByArr = dictionaryToArray(groupByList); const pivotGroupByArr = dictionaryToArray(groupByList);
const pivotQuery = useKQL ? getPivotQuery(searchQuery) : getPivotQuery(searchString); const pivotQuery = getPivotQuery(searchQuery);
// Advanced editor for pivot config state // Advanced editor for pivot config state
const [isAdvancedEditorSwitchModalVisible, setAdvancedEditorSwitchModalVisible] = useState(false); const [isAdvancedEditorSwitchModalVisible, setAdvancedEditorSwitchModalVisible] = useState(false);
@ -409,8 +438,6 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
const applyAdvancedSourceEditorChanges = () => { const applyAdvancedSourceEditorChanges = () => {
const sourceConfig = JSON.parse(advancedEditorSourceConfig); const sourceConfig = JSON.parse(advancedEditorSourceConfig);
const prettySourceConfig = JSON.stringify(sourceConfig, null, 2); const prettySourceConfig = JSON.stringify(sourceConfig, null, 2);
// Switched to editor so we clear out the search string as the bar won't be visible
setSearchString(emptySearch);
setSearchQuery(sourceConfig); setSearchQuery(sourceConfig);
setSourceConfigUpdated(true); setSourceConfigUpdated(true);
setAdvancedEditorSourceConfig(prettySourceConfig); setAdvancedEditorSourceConfig(prettySourceConfig);
@ -471,7 +498,6 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
const toggleAdvancedSourceEditor = (reset = false) => { const toggleAdvancedSourceEditor = (reset = false) => {
if (reset === true) { if (reset === true) {
setSearchQuery(defaultSearch); setSearchQuery(defaultSearch);
setSearchString(defaultSearch);
setSourceConfigUpdated(false); setSourceConfigUpdated(false);
} }
if (isAdvancedSourceEditorEnabled === false) { if (isAdvancedSourceEditorEnabled === false) {
@ -532,6 +558,7 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
groupByList, groupByList,
isAdvancedPivotEditorEnabled, isAdvancedPivotEditorEnabled,
isAdvancedSourceEditorEnabled, isAdvancedSourceEditorEnabled,
searchLanguage,
searchString, searchString,
searchQuery, searchQuery,
sourceConfigUpdated, sourceConfigUpdated,
@ -544,6 +571,7 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
JSON.stringify(pivotGroupByArr), JSON.stringify(pivotGroupByArr),
isAdvancedPivotEditorEnabled, isAdvancedPivotEditorEnabled,
isAdvancedSourceEditorEnabled, isAdvancedSourceEditorEnabled,
searchLanguage,
searchString, searchString,
searchQuery, searchQuery,
valid, valid,
@ -560,7 +588,7 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
<EuiFlexItem grow={false} className="transform__stepDefineFormLeftColumn"> <EuiFlexItem grow={false} className="transform__stepDefineFormLeftColumn">
<div data-test-subj="transformStepDefineForm"> <div data-test-subj="transformStepDefineForm">
<EuiForm> <EuiForm>
{searchItems.savedSearch === undefined && typeof searchString === 'string' && ( {searchItems.savedSearch === undefined && (
<Fragment> <Fragment>
<EuiFormRow <EuiFormRow
label={i18n.translate('xpack.transform.stepDefineForm.indexPatternLabel', { label={i18n.translate('xpack.transform.stepDefineForm.indexPatternLabel', {
@ -592,18 +620,32 @@ export const StepDefineForm: FC<Props> = React.memo(({ overrides = {}, onChange,
defaultMessage: 'Use a query to filter the source data (optional).', defaultMessage: 'Use a query to filter the source data (optional).',
})} })}
> >
<KqlFilterBar <QueryStringInput
indexPattern={indexPattern} bubbleSubmitEvent={true}
onSubmit={searchHandler} query={searchInput}
initialValue={searchString === defaultSearch ? emptySearch : searchString} indexPatterns={[indexPattern]}
placeholder={i18n.translate( onChange={searchChangeHandler}
'xpack.transform.stepDefineForm.queryPlaceholder', onSubmit={searchSubmitHandler}
{ placeholder={
defaultMessage: 'e.g. {example}', searchInput.language === QUERY_LANGUAGE_KUERY
values: { example: 'method : "GET" or status : "404"' }, ? i18n.translate(
} 'xpack.transform.stepDefineForm.queryPlaceholderKql',
)} {
testSubj="tarnsformQueryInput" defaultMessage: 'e.g. {example}',
values: { example: 'method : "GET" or status : "404"' },
}
)
: i18n.translate(
'xpack.transform.stepDefineForm.queryPlaceholderLucene',
{
defaultMessage: 'e.g. {example}',
values: { example: 'method:GET OR status:404' },
}
)
}
disableAutoFocus={true}
dataTestSubj="transformQueryInput"
languageSwitcherPopoverAnchorPosition="rightDown"
/> />
</EuiFormRow> </EuiFormRow>
)} )}

View file

@ -8,7 +8,6 @@ import React from 'react';
import { render, wait } from '@testing-library/react'; import { render, wait } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect'; import '@testing-library/jest-dom/extend-expect';
import { Providers } from '../../../../app_dependencies.mock';
import { import {
PivotAggsConfig, PivotAggsConfig,
PivotGroupByConfig, PivotGroupByConfig,
@ -20,8 +19,8 @@ import { SearchItems } from '../../../../hooks/use_search_items';
import { StepDefineExposedState } from './step_define_form'; import { StepDefineExposedState } from './step_define_form';
import { StepDefineSummary } from './step_define_summary'; import { StepDefineSummary } from './step_define_summary';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports'); jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
describe('Transform: <DefinePivotSummary />', () => { describe('Transform: <DefinePivotSummary />', () => {
// Using the async/await wait()/done() pattern to avoid act() errors. // Using the async/await wait()/done() pattern to avoid act() errors.
@ -51,15 +50,14 @@ describe('Transform: <DefinePivotSummary />', () => {
isAdvancedPivotEditorEnabled: false, isAdvancedPivotEditorEnabled: false,
isAdvancedSourceEditorEnabled: false, isAdvancedSourceEditorEnabled: false,
sourceConfigUpdated: false, sourceConfigUpdated: false,
searchLanguage: 'kuery',
searchString: 'the-query', searchString: 'the-query',
searchQuery: 'the-search-query', searchQuery: 'the-search-query',
valid: true, valid: true,
}; };
const { getByText } = render( const { getByText } = render(
<Providers> <StepDefineSummary formState={formState} searchItems={searchItems as SearchItems} />
<StepDefineSummary formState={formState} searchItems={searchItems as SearchItems} />
</Providers>
); );
// Act // Act

View file

@ -26,9 +26,6 @@ import { GroupByListSummary } from '../group_by_list';
import { StepDefineExposedState } from './step_define_form'; import { StepDefineExposedState } from './step_define_form';
const defaultSearch = '*';
const emptySearch = '';
interface Props { interface Props {
formState: StepDefineExposedState; formState: StepDefineExposedState;
searchItems: SearchItems; searchItems: SearchItems;
@ -39,66 +36,50 @@ export const StepDefineSummary: FC<Props> = ({
searchItems, searchItems,
}) => { }) => {
const pivotQuery = getPivotQuery(searchQuery); const pivotQuery = getPivotQuery(searchQuery);
let useCodeBlock = false;
let displaySearch;
// searchString set to empty once source config editor used - display query instead
if (searchString === emptySearch) {
displaySearch = JSON.stringify(searchQuery, null, 2);
useCodeBlock = true;
} else if (searchString === defaultSearch) {
displaySearch = emptySearch;
} else {
displaySearch = searchString;
}
return ( return (
<EuiFlexGroup> <EuiFlexGroup>
<EuiFlexItem grow={false} style={{ minWidth: '420px' }}> <EuiFlexItem grow={false} style={{ minWidth: '420px' }}>
<div data-test-subj="transformStepDefineSummary"> <div data-test-subj="transformStepDefineSummary">
<EuiForm> <EuiForm>
{searchItems.savedSearch !== undefined && {searchItems.savedSearch === undefined && (
searchItems.savedSearch.id === undefined && <Fragment>
typeof searchString === 'string' && ( <EuiFormRow
<Fragment> label={i18n.translate('xpack.transform.stepDefineSummary.indexPatternLabel', {
defaultMessage: 'Index pattern',
})}
>
<span>{searchItems.indexPattern.title}</span>
</EuiFormRow>
{typeof searchString === 'string' && (
<EuiFormRow <EuiFormRow
label={i18n.translate('xpack.transform.stepDefineSummary.indexPatternLabel', { label={i18n.translate('xpack.transform.stepDefineSummary.queryLabel', {
defaultMessage: 'Index pattern', defaultMessage: 'Query',
})} })}
> >
<span>{searchItems.indexPattern.title}</span> <span>{searchString}</span>
</EuiFormRow> </EuiFormRow>
{useCodeBlock === false && displaySearch !== emptySearch && ( )}
<EuiFormRow {typeof searchString === 'undefined' && (
label={i18n.translate('xpack.transform.stepDefineSummary.queryLabel', { <EuiFormRow
defaultMessage: 'Query', label={i18n.translate('xpack.transform.stepDefineSummary.queryCodeBlockLabel', {
})} defaultMessage: 'Query',
})}
>
<EuiCodeBlock
language="js"
fontSize="s"
paddingSize="s"
color="light"
overflowHeight={300}
isCopyable
> >
<span>{displaySearch}</span> {JSON.stringify(searchQuery, null, 2)}
</EuiFormRow> </EuiCodeBlock>
)} </EuiFormRow>
{useCodeBlock === true && displaySearch !== emptySearch && ( )}
<EuiFormRow </Fragment>
label={i18n.translate( )}
'xpack.transform.stepDefineSummary.queryCodeBlockLabel',
{
defaultMessage: 'Query',
}
)}
>
<EuiCodeBlock
language="js"
fontSize="s"
paddingSize="s"
color="light"
overflowHeight={300}
isCopyable
>
{displaySearch}
</EuiCodeBlock>
</EuiFormRow>
)}
</Fragment>
)}
{searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && ( {searchItems.savedSearch !== undefined && searchItems.savedSearch.id !== undefined && (
<EuiFormRow <EuiFormRow

View file

@ -34,7 +34,7 @@ const pivotModalMessage = i18n.translate(
const sourceModalMessage = i18n.translate( const sourceModalMessage = i18n.translate(
'xpack.transform.stepDefineForm.advancedSourceEditorSwitchModalBodyText', 'xpack.transform.stepDefineForm.advancedSourceEditorSwitchModalBodyText',
{ {
defaultMessage: `By switching back to KQL query bar you will lose your edits.`, defaultMessage: `By switching back to the query bar you will lose your edits.`,
} }
); );
const pivotModalConfirmButtonText = i18n.translate( const pivotModalConfirmButtonText = i18n.translate(
@ -46,7 +46,7 @@ const pivotModalConfirmButtonText = i18n.translate(
const sourceModalConfirmButtonText = i18n.translate( const sourceModalConfirmButtonText = i18n.translate(
'xpack.transform.stepDefineForm.advancedSourceEditorSwitchModalConfirmButtonText', 'xpack.transform.stepDefineForm.advancedSourceEditorSwitchModalConfirmButtonText',
{ {
defaultMessage: 'Switch to KQL', defaultMessage: 'Switch to query bar',
} }
); );
const cancelButtonText = i18n.translate( const cancelButtonText = i18n.translate(

View file

@ -7,15 +7,13 @@
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import React from 'react'; import React from 'react';
import { Providers } from '../../../../app_dependencies.mock';
import { TransformListRow } from '../../../../common'; import { TransformListRow } from '../../../../common';
import { DeleteAction } from './action_delete'; import { DeleteAction } from './action_delete';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports'); jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
describe('Transform: Transform List Actions <DeleteAction />', () => { describe('Transform: Transform List Actions <DeleteAction />', () => {
test('Minimal initialization', () => { test('Minimal initialization', () => {
@ -26,14 +24,7 @@ describe('Transform: Transform List Actions <DeleteAction />', () => {
deleteTransform(d: TransformListRow) {}, deleteTransform(d: TransformListRow) {},
}; };
const wrapper = shallow( const wrapper = shallow(<DeleteAction {...props} />);
<Providers>
<DeleteAction {...props} />
</Providers>
)
.find(DeleteAction)
.shallow();
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });

View file

@ -7,15 +7,13 @@
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import React from 'react'; import React from 'react';
import { Providers } from '../../../../app_dependencies.mock';
import { TransformListRow } from '../../../../common'; import { TransformListRow } from '../../../../common';
import { StartAction } from './action_start'; import { StartAction } from './action_start';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports'); jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
describe('Transform: Transform List Actions <StartAction />', () => { describe('Transform: Transform List Actions <StartAction />', () => {
test('Minimal initialization', () => { test('Minimal initialization', () => {
@ -26,13 +24,7 @@ describe('Transform: Transform List Actions <StartAction />', () => {
startTransform(d: TransformListRow) {}, startTransform(d: TransformListRow) {},
}; };
const wrapper = shallow( const wrapper = shallow(<StartAction {...props} />);
<Providers>
<StartAction {...props} />
</Providers>
)
.find(StartAction)
.shallow();
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });

View file

@ -7,15 +7,13 @@
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import React from 'react'; import React from 'react';
import { Providers } from '../../../../app_dependencies.mock';
import { TransformListRow } from '../../../../common'; import { TransformListRow } from '../../../../common';
import { StopAction } from './action_stop'; import { StopAction } from './action_stop';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
jest.mock('ui/new_platform');
jest.mock('../../../../../shared_imports'); jest.mock('../../../../../shared_imports');
jest.mock('../../../../../app/app_dependencies');
describe('Transform: Transform List Actions <StopAction />', () => { describe('Transform: Transform List Actions <StopAction />', () => {
test('Minimal initialization', () => { test('Minimal initialization', () => {
@ -26,13 +24,7 @@ describe('Transform: Transform List Actions <StopAction />', () => {
stopTransform(d: TransformListRow) {}, stopTransform(d: TransformListRow) {},
}; };
const wrapper = shallow( const wrapper = shallow(<StopAction {...props} />);
<Providers>
<StopAction {...props} />
</Providers>
)
.find(StopAction)
.shallow();
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });

View file

@ -9,12 +9,16 @@ import { CoreSetup } from 'src/core/public';
import { DataPublicPluginStart } from 'src/plugins/data/public'; import { DataPublicPluginStart } from 'src/plugins/data/public';
import { ManagementSetup } from 'src/plugins/management/public'; import { ManagementSetup } from 'src/plugins/management/public';
import { Storage } from '../../../../src/plugins/kibana_utils/public';
import { renderApp } from './app/app'; import { renderApp } from './app/app';
import { AppDependencies } from './app/app_dependencies'; import { AppDependencies } from './app/app_dependencies';
import { breadcrumbService } from './app/services/navigation'; import { breadcrumbService } from './app/services/navigation';
import { docTitleService } from './app/services/navigation'; import { docTitleService } from './app/services/navigation';
import { textService } from './app/services/text'; import { textService } from './app/services/text';
const localStorage = new Storage(window.localStorage);
export interface PluginsDependencies { export interface PluginsDependencies {
data: DataPublicPluginStart; data: DataPublicPluginStart;
management: ManagementSetup; management: ManagementSetup;
@ -56,6 +60,7 @@ export class TransformUiPlugin {
notifications, notifications,
overlays, overlays,
savedObjects, savedObjects,
storage: localStorage,
uiSettings, uiSettings,
}; };

View file

@ -12,20 +12,6 @@ export {
} from '../../../../src/plugins/es_ui_shared/console_lang/lib'; } from '../../../../src/plugins/es_ui_shared/console_lang/lib';
export { export {
SendRequestConfig,
SendRequestResponse,
UseRequestConfig, UseRequestConfig,
sendRequest,
useRequest, useRequest,
} from '../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; } from '../../../../src/plugins/es_ui_shared/public/request/np_ready_request';
export {
CronEditor,
DAY,
} from '../../../../src/plugins/es_ui_shared/public/components/cron_editor';
// Needs to be imported because we're reusing KqlFilterBar which depends on it.
export { setDependencyCache } from '../../../legacy/plugins/ml/public/application/util/dependency_cache';
// @ts-ignore: could not find declaration file for module
export { KqlFilterBar } from '../../../legacy/plugins/ml/public/application/components/kql_filter_bar';

View file

@ -12428,7 +12428,6 @@
"xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage": "「{groupByListName}」とネスティングの矛盾があるため、構成「{aggName}」を追加できませんでした。", "xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage": "「{groupByListName}」とネスティングの矛盾があるため、構成「{aggName}」を追加できませんでした。",
"xpack.transform.stepDefineForm.queryHelpText": "クエリ文字列でソースデータをフィルタリングしてください (オプション)。", "xpack.transform.stepDefineForm.queryHelpText": "クエリ文字列でソースデータをフィルタリングしてください (オプション)。",
"xpack.transform.stepDefineForm.queryLabel": "クエリ", "xpack.transform.stepDefineForm.queryLabel": "クエリ",
"xpack.transform.stepDefineForm.queryPlaceholder": "例: {example}.",
"xpack.transform.stepDefineForm.savedSearchLabel": "保存検索", "xpack.transform.stepDefineForm.savedSearchLabel": "保存検索",
"xpack.transform.stepDefineSummary.aggregationsLabel": "アグリゲーション(集計)", "xpack.transform.stepDefineSummary.aggregationsLabel": "アグリゲーション(集計)",
"xpack.transform.stepDefineSummary.groupByLabel": "グループ分けの条件", "xpack.transform.stepDefineSummary.groupByLabel": "グループ分けの条件",

View file

@ -12428,7 +12428,6 @@
"xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage": "无法添加配置“{aggName}”,因为与“{groupByListName}”有嵌套冲突。", "xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage": "无法添加配置“{aggName}”,因为与“{groupByListName}”有嵌套冲突。",
"xpack.transform.stepDefineForm.queryHelpText": "使用查询字符串筛选源数据(可选)。", "xpack.transform.stepDefineForm.queryHelpText": "使用查询字符串筛选源数据(可选)。",
"xpack.transform.stepDefineForm.queryLabel": "查询", "xpack.transform.stepDefineForm.queryLabel": "查询",
"xpack.transform.stepDefineForm.queryPlaceholder": "例如,{example}",
"xpack.transform.stepDefineForm.savedSearchLabel": "已保存搜索", "xpack.transform.stepDefineForm.savedSearchLabel": "已保存搜索",
"xpack.transform.stepDefineSummary.aggregationsLabel": "聚合", "xpack.transform.stepDefineSummary.aggregationsLabel": "聚合",
"xpack.transform.stepDefineSummary.groupByLabel": "分组依据", "xpack.transform.stepDefineSummary.groupByLabel": "分组依据",

View file

@ -160,15 +160,15 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) {
}, },
async assertQueryInputExists() { async assertQueryInputExists() {
await testSubjects.existOrFail('tarnsformQueryInput'); await testSubjects.existOrFail('transformQueryInput');
}, },
async assertQueryInputMissing() { async assertQueryInputMissing() {
await testSubjects.missingOrFail('tarnsformQueryInput'); await testSubjects.missingOrFail('transformQueryInput');
}, },
async assertQueryValue(expectedQuery: string) { async assertQueryValue(expectedQuery: string) {
const actualQuery = await testSubjects.getVisibleText('tarnsformQueryInput'); const actualQuery = await testSubjects.getVisibleText('transformQueryInput');
expect(actualQuery).to.eql( expect(actualQuery).to.eql(
expectedQuery, expectedQuery,
`Query input text should be '${expectedQuery}' (got ${actualQuery})` `Query input text should be '${expectedQuery}' (got ${actualQuery})`