Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[ML] Transforms: Adds clone feature to transforms list. #57837

Merged
merged 8 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions x-pack/legacy/plugins/transform/public/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { getAppProviders } from './app_dependencies';
import { AuthorizationContext } from './lib/authorization';
import { AppDependencies } from '../shim';

import { CloneTransformSection } from './sections/clone_transform';
import { CreateTransformSection } from './sections/create_transform';
import { TransformManagementSection } from './sections/transform_management';

Expand All @@ -39,6 +40,10 @@ export const App: FC = () => {
return (
<div data-test-subj="transformApp">
<Switch>
<Route
path={`${CLIENT_BASE_PATH}/${SECTION_SLUG.CLONE_TRANSFORM}/:transformId`}
component={CloneTransformSection}
/>
<Route
path={`${CLIENT_BASE_PATH}/${SECTION_SLUG.CREATE_TRANSFORM}/:savedObjectId`}
component={CreateTransformSection}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const CLIENT_BASE_PATH = '/management/elasticsearch/transform';

export enum SECTION_SLUG {
HOME = 'transform_management',
CLONE_TRANSFORM = 'clone_transform',
CREATE_TRANSFORM = 'create_transform',
}

Expand Down
26 changes: 21 additions & 5 deletions x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/public';
import { SavedObjectsClientContract, SimpleSavedObject, IUiSettingsClient } from 'src/core/public';
import {
IndexPattern,
esQuery,
Expand All @@ -14,7 +14,7 @@ import {
type IndexPatternId = string;
type SavedSearchId = string;

let indexPatternCache = [];
let indexPatternCache: Array<SimpleSavedObject<Record<string, any>>> = [];
let fullIndexPatterns;
let currentIndexPattern = null;
let currentSavedSearch = null;
Expand Down Expand Up @@ -53,6 +53,10 @@ export function loadIndexPatterns(
});
}

export function getIndexPatternIdByTitle(indexPatternTitle: string): string | undefined {
return indexPatternCache.find(d => d?.attributes?.title === indexPatternTitle)?.id;
}

type CombinedQuery = Record<'bool', any> | unknown;

export function loadCurrentIndexPattern(
Expand All @@ -69,12 +73,20 @@ export function loadCurrentSavedSearch(savedSearches: any, savedSearchId: SavedS
return currentSavedSearch;
}

function isIndexPattern(arg: any): arg is IndexPattern {
return arg !== undefined;
}
// Helper for creating the items used for searching and job creation.
export function createSearchItems(
indexPattern: IndexPattern | undefined,
savedSearch: any,
config: IUiSettingsClient
) {
): {
indexPattern: IndexPattern;
savedSearch: any;
query: any;
combinedQuery: CombinedQuery;
} {
// query is only used by the data visualizer as it needs
// a lucene query_string.
// Using a blank query will cause match_all:{} to be used
Expand All @@ -94,9 +106,9 @@ export function createSearchItems(
},
};

if (indexPattern === undefined && savedSearch !== null && savedSearch.id !== undefined) {
if (!isIndexPattern(indexPattern) && savedSearch !== null && savedSearch.id !== undefined) {
const searchSource = savedSearch.searchSource;
indexPattern = searchSource.getField('index');
indexPattern = searchSource.getField('index') as IndexPattern;

query = searchSource.getField('query');
const fs = searchSource.getField('filter');
Expand All @@ -107,6 +119,10 @@ export function createSearchItems(
combinedQuery = esQuery.buildEsQuery(indexPattern, [query], filters, esQueryConfigs);
}

if (!isIndexPattern(indexPattern)) {
throw new Error('Index Pattern not defined');
}

return {
indexPattern,
savedSearch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

export { getIndexPatternIdByTitle, loadIndexPatterns } from './common';
export {
useKibanaContext,
InitializedKibanaContextValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,21 @@ import {
IndexPattern,
IndexPatternsContract,
} from '../../../../../../../../src/plugins/data/public';
import { KibanaConfig } from '../../../../../../../../src/legacy/server/kbn_server';

// set() method is missing in original d.ts
interface KibanaConfigTypeFix extends KibanaConfig {
set(key: string, value: any): void;
}
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { IUiSettingsClient } from '../../../../../../../../src/core/public/ui_settings';

interface UninitializedKibanaContextValue {
initialized: boolean;
initialized: false;
}

export interface InitializedKibanaContextValue {
combinedQuery: any;
currentIndexPattern: IndexPattern;
currentSavedSearch: SavedSearch;
indexPatterns: IndexPatternsContract;
initialized: boolean;
initialized: true;
kbnBaseUrl: string;
kibanaConfig: KibanaConfigTypeFix;
kibanaConfig: IUiSettingsClient;
currentIndexPattern: IndexPattern;
currentSavedSearch?: SavedSearch;
}

export type KibanaContextValue = UninitializedKibanaContextValue | InitializedKibanaContextValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
loadCurrentSavedSearch,
} from './common';

import { KibanaContext, KibanaContextValue } from './kibana_context';
import { InitializedKibanaContextValue, KibanaContext, KibanaContextValue } from './kibana_context';

const indexPatterns = npStart.plugins.data.indexPatterns;
const savedObjectsClient = npStart.core.savedObjects.client;
Expand Down Expand Up @@ -52,20 +52,20 @@ export const KibanaProvider: FC<Props> = ({ savedObjectId, children }) => {

const kibanaConfig = npStart.core.uiSettings;

const { indexPattern, savedSearch, combinedQuery } = createSearchItems(
fetchedIndexPattern,
fetchedSavedSearch,
kibanaConfig
);

const kibanaContext = {
const {
indexPattern: currentIndexPattern,
savedSearch: currentSavedSearch,
combinedQuery,
currentIndexPattern: indexPattern,
currentSavedSearch: savedSearch,
} = createSearchItems(fetchedIndexPattern, fetchedSavedSearch, kibanaConfig);

const kibanaContext: InitializedKibanaContextValue = {
indexPatterns,
initialized: true,
kbnBaseUrl: npStart.core.injectedMetadata.getBasePath(),
kibanaConfig,
combinedQuery,
currentIndexPattern,
currentSavedSearch,
};

setContextValue(kibanaContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* 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 React, { useEffect, useState, FC } from 'react';
import { RouteComponentProps } from 'react-router-dom';

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

import {
EuiBetaBadge,
EuiButtonEmpty,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiPageContent,
EuiPageContentBody,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';

import { npStart } from 'ui/new_platform';

import { useApi } from '../../hooks/use_api';

import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
import { TransformPivotConfig } from '../../common';
import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation';
import { documentationLinksService } from '../../services/documentation';
import { PrivilegesWrapper } from '../../lib/authorization';
import {
getIndexPatternIdByTitle,
loadIndexPatterns,
KibanaProvider,
RenderOnlyWithInitializedKibanaContext,
} from '../../lib/kibana';

import { Wizard } from '../create_transform/components/wizard';

const indexPatterns = npStart.plugins.data.indexPatterns;
const savedObjectsClient = npStart.core.savedObjects.client;

interface GetTransformsResponseOk {
count: number;
transforms: TransformPivotConfig[];
}

interface GetTransformsResponseError {
error: {
msg: string;
path: string;
query: any;
statusCode: number;
response: string;
};
}

function isGetTransformsResponseError(arg: any): arg is GetTransformsResponseError {
return arg.error !== undefined;
}

type GetTransformsResponse = GetTransformsResponseOk | GetTransformsResponseError;

type Props = RouteComponentProps<{ transformId: string }>;
export const CloneTransformSection: FC<Props> = ({ match }) => {
// Set breadcrumb and page title
useEffect(() => {
breadcrumbService.setBreadcrumbs(BREADCRUMB_SECTION.CLONE_TRANSFORM);
docTitleService.setTitle('createTransform');
}, []);

const api = useApi();

const transformId = match.params.transformId;

const [transformConfig, setTransformConfig] = useState<TransformPivotConfig>();
const [errorMessage, setErrorMessage] = useState();
const [isInitialized, setIsInitialized] = useState(false);
const [savedObjectId, setSavedObjectId] = useState<string | undefined>(undefined);

const fetchTransformConfig = async () => {
try {
const transformConfigs: GetTransformsResponse = await api.getTransforms(transformId);
if (isGetTransformsResponseError(transformConfigs)) {
setTransformConfig(undefined);
setErrorMessage(transformConfigs.error.msg);
setIsInitialized(true);
return;
}

await loadIndexPatterns(savedObjectsClient, indexPatterns);
const indexPatternTitle = Array.isArray(transformConfigs.transforms[0].source.index)
? transformConfigs.transforms[0].source.index.join(',')
: transformConfigs.transforms[0].source.index;
const indexPatternId = getIndexPatternIdByTitle(indexPatternTitle);

if (indexPatternId === undefined) {
throw new Error(
i18n.translate('xpack.transform.clone.errorPromptText', {
defaultMessage: 'Could not fetch the Kibana index pattern ID.',
})
);
}

setSavedObjectId(indexPatternId);

setTransformConfig(transformConfigs.transforms[0]);
setErrorMessage(undefined);
setIsInitialized(true);
} catch (e) {
setTransformConfig(undefined);
if (e.message !== undefined) {
setErrorMessage(e.message);
} else {
setErrorMessage(JSON.stringify(e, null, 2));
}
setIsInitialized(true);
}
};

useEffect(() => {
fetchTransformConfig();
// The effect should only be called once.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<PrivilegesWrapper privileges={APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES}>
<EuiPageContent data-test-subj="transformPageCloneTransform">
<EuiTitle size="l">
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={true}>
<h1>
<FormattedMessage
id="xpack.transform.transformsWizard.cloneTransformTitle"
defaultMessage="Clone transform"
/>
<span>&nbsp;</span>
<EuiBetaBadge
label={i18n.translate('xpack.transform.transformsWizard.betaBadgeLabel', {
defaultMessage: `Beta`,
})}
tooltipContent={i18n.translate(
'xpack.transform.transformsWizard.betaBadgeTooltipContent',
{
defaultMessage: `Transforms are a beta feature. We'd love to hear your feedback.`,
}
)}
/>
</h1>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
href={documentationLinksService.getTransformsDocUrl()}
target="_blank"
iconType="help"
data-test-subj="documentationLink"
>
<FormattedMessage
id="xpack.transform.transformsWizard.transformDocsLinkText"
defaultMessage="Transform docs"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiTitle>
<EuiPageContentBody>
<EuiSpacer size="l" />
{typeof errorMessage !== 'undefined' && (
<EuiCallOut
title={i18n.translate('xpack.transform.clone.errorPromptTitle', {
defaultMessage: 'An error occurred getting the transform configuration.',
})}
color="danger"
iconType="alert"
>
<pre>{JSON.stringify(errorMessage)}</pre>
</EuiCallOut>
)}
{savedObjectId !== undefined && isInitialized === true && transformConfig !== undefined && (
<KibanaProvider savedObjectId={savedObjectId}>
<RenderOnlyWithInitializedKibanaContext>
<Wizard cloneConfig={transformConfig} />
</RenderOnlyWithInitializedKibanaContext>
</KibanaProvider>
)}
</EuiPageContentBody>
</EuiPageContent>
</PrivilegesWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { CloneTransformSection } from './clone_transform_section';
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
*/

export {
applyTransformConfigToDefineState,
getDefaultStepDefineState,
StepDefineExposedState,
StepDefineForm,
getDefaultStepDefineState,
} from './step_define_form';
export { StepDefineSummary } from './step_define_summary';
Loading