diff --git a/x-pack/legacy/plugins/transform/public/app/app.tsx b/x-pack/legacy/plugins/transform/public/app/app.tsx
index 825c1761bf619..0f21afbcccca8 100644
--- a/x-pack/legacy/plugins/transform/public/app/app.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/app.tsx
@@ -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';
@@ -39,6 +40,10 @@ export const App: FC = () => {
return (
+
{
+ test('isMatchAllQuery()', () => {
+ expect(isMatchAllQuery(defaultQuery)).toBe(false);
+ expect(isMatchAllQuery(matchAllQuery)).toBe(true);
+ expect(isMatchAllQuery(simpleQuery)).toBe(false);
+ });
+
test('isSimpleQuery()', () => {
expect(isSimpleQuery(defaultQuery)).toBe(true);
expect(isSimpleQuery(matchAllQuery)).toBe(false);
diff --git a/x-pack/legacy/plugins/transform/public/app/common/request.ts b/x-pack/legacy/plugins/transform/public/app/common/request.ts
index 5d508f3d245d3..3b740de177ef8 100644
--- a/x-pack/legacy/plugins/transform/public/app/common/request.ts
+++ b/x-pack/legacy/plugins/transform/public/app/common/request.ts
@@ -53,6 +53,12 @@ export function isSimpleQuery(arg: any): arg is SimpleQuery {
return arg.query_string !== undefined;
}
+export const matchAllQuery = { match_all: {} };
+export function isMatchAllQuery(query: any): boolean {
+ return query.match_all !== undefined && Object.keys(query.match_all).length === 0;
+}
+
+export const defaultQuery: PivotQuery = { query_string: { query: '*' } };
export function isDefaultQuery(query: PivotQuery): boolean {
return isSimpleQuery(query) && query.query_string.query === '*';
}
diff --git a/x-pack/legacy/plugins/transform/public/app/constants/index.ts b/x-pack/legacy/plugins/transform/public/app/constants/index.ts
index 85ffc222f59a2..78b5f018dd782 100644
--- a/x-pack/legacy/plugins/transform/public/app/constants/index.ts
+++ b/x-pack/legacy/plugins/transform/public/app/constants/index.ts
@@ -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',
}
diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts
index 3e55d509a94ab..aba61766b5d2b 100644
--- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts
+++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts
@@ -4,17 +4,19 @@
* 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,
IndexPatternsContract,
} from '../../../../../../../../src/plugins/data/public';
+import { matchAllQuery } from '../../common';
+
type IndexPatternId = string;
type SavedSearchId = string;
-let indexPatternCache = [];
+let indexPatternCache: Array>> = [];
let fullIndexPatterns;
let currentIndexPattern = null;
let currentSavedSearch = null;
@@ -53,6 +55,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(
@@ -69,12 +75,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
@@ -86,17 +100,13 @@ export function createSearchItems(
let combinedQuery: CombinedQuery = {
bool: {
- must: [
- {
- match_all: {},
- },
- ],
+ must: [matchAllQuery],
},
};
- 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');
@@ -107,6 +117,10 @@ export function createSearchItems(
combinedQuery = esQuery.buildEsQuery(indexPattern, [query], filters, esQueryConfigs);
}
+ if (!isIndexPattern(indexPattern)) {
+ throw new Error('Index Pattern is not defined.');
+ }
+
return {
indexPattern,
savedSearch,
diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts
index 82d5362e21c02..62107cb37ff2c 100644
--- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts
+++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts
@@ -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,
diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx
index 5b7702a0193ec..b0a0371d2de86 100644
--- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx
@@ -6,30 +6,26 @@
import React, { createContext, useContext, FC } from 'react';
+import { IUiSettingsClient } from 'kibana/public';
+
import { SavedSearch } from '../../../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/types';
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;
-}
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;
diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx
index 0a9de49168ad4..d2cf5f2b32910 100644
--- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx
@@ -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;
@@ -52,20 +52,20 @@ export const KibanaProvider: FC = ({ 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);
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
new file mode 100644
index 0000000000000..de96a4de32962
--- /dev/null
+++ b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
@@ -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 = ({ 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();
+ const [errorMessage, setErrorMessage] = useState();
+ const [isInitialized, setIsInitialized] = useState(false);
+ const [savedObjectId, setSavedObjectId] = useState(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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {typeof errorMessage !== 'undefined' && (
+
+ {JSON.stringify(errorMessage)}
+
+ )}
+ {savedObjectId !== undefined && isInitialized === true && transformConfig !== undefined && (
+
+
+
+
+
+ )}
+
+
+
+ );
+};
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/index.ts b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/index.ts
new file mode 100644
index 0000000000000..fef33d50130a7
--- /dev/null
+++ b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/index.ts
@@ -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';
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts
index 3fcc3cc15803b..e5c6783db1022 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts
@@ -17,6 +17,7 @@ import {
getDefaultSelectableFields,
getFlattenedFields,
isDefaultQuery,
+ matchAllQuery,
EsDoc,
EsDocSource,
EsFieldName,
@@ -75,7 +76,7 @@ export const useSourceIndexData = (
index: indexPattern.title,
size: SEARCH_SIZE,
// Instead of using the default query (`*`), fall back to a more efficient `match_all` query.
- body: { query: isDefaultQuery(query) ? { match_all: {} } : query },
+ body: { query: isDefaultQuery(query) ? matchAllQuery : query },
});
if (isErrorResponse(resp)) {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts
index 7c5b60715961b..881e8c6b26658 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts
@@ -5,8 +5,9 @@
*/
export {
+ applyTransformConfigToDefineState,
+ getDefaultStepDefineState,
StepDefineExposedState,
StepDefineForm,
- getDefaultStepDefineState,
} from './step_define_form';
export { StepDefineSummary } from './step_define_summary';
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
index b8f63ef697e78..675386be8e2a5 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { isEqual } from 'lodash';
import React, { Fragment, FC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
@@ -27,7 +28,8 @@ import {
EuiSwitch,
} from '@elastic/eui';
-import { dictionaryToArray } from '../../../../../../common/types/common';
+import { TransformPivotConfig } from '../../../../common';
+import { dictionaryToArray, Dictionary } from '../../../../../../common/types/common';
import { DropDown } from '../aggregation_dropdown';
import { AggListForm } from '../aggregation_list';
import { GroupByListForm } from '../group_by_list';
@@ -43,10 +45,12 @@ import {
} from '../../../../lib/kibana';
import {
- AggName,
- DropDownLabel,
getPivotQuery,
getPreviewRequestBody,
+ isMatchAllQuery,
+ matchAllQuery,
+ AggName,
+ DropDownLabel,
PivotAggDict,
PivotAggsConfig,
PivotAggsConfigDict,
@@ -55,6 +59,7 @@ import {
PivotGroupByConfigDict,
PivotSupportedGroupByAggs,
PIVOT_SUPPORTED_AGGS,
+ PIVOT_SUPPORTED_GROUP_BY_AGGS,
} from '../../../../common';
import { getPivotDropdownOptions } from './common';
@@ -89,6 +94,58 @@ export function getDefaultStepDefineState(
valid: false,
};
}
+
+export function applyTransformConfigToDefineState(
+ state: StepDefineExposedState,
+ transformConfig?: TransformPivotConfig
+): StepDefineExposedState {
+ // apply the transform configuration to wizard DEFINE state
+ if (transformConfig !== undefined) {
+ // transform aggregations config to wizard state
+ state.aggList = Object.keys(transformConfig.pivot.aggregations).reduce((aggList, aggName) => {
+ const aggConfig = transformConfig.pivot.aggregations[aggName] as Dictionary;
+ const agg = Object.keys(aggConfig)[0];
+ aggList[aggName] = {
+ ...aggConfig[agg],
+ agg: agg as PIVOT_SUPPORTED_AGGS,
+ aggName,
+ dropDownName: aggName,
+ } as PivotAggsConfig;
+ return aggList;
+ }, {} as PivotAggsConfigDict);
+
+ // transform group by config to wizard state
+ state.groupByList = Object.keys(transformConfig.pivot.group_by).reduce(
+ (groupByList, groupByName) => {
+ const groupByConfig = transformConfig.pivot.group_by[groupByName] as Dictionary;
+ const groupBy = Object.keys(groupByConfig)[0];
+ groupByList[groupByName] = {
+ agg: groupBy as PIVOT_SUPPORTED_GROUP_BY_AGGS,
+ aggName: groupByName,
+ dropDownName: groupByName,
+ ...groupByConfig[groupBy],
+ } as PivotGroupByConfig;
+ return groupByList;
+ },
+ {} as PivotGroupByConfigDict
+ );
+
+ // only apply the query from the transform config to wizard state if it's not the default query
+ const query = transformConfig.source.query;
+ if (query !== undefined && !isEqual(query, matchAllQuery)) {
+ state.isAdvancedSourceEditorEnabled = true;
+ state.searchString = '';
+ state.searchQuery = query;
+ state.sourceConfigUpdated = true;
+ }
+
+ // applying a transform config to wizard state will always result in a valid configuration
+ state.valid = true;
+ }
+
+ return state;
+}
+
export function isAggNameConflict(
aggName: AggName,
aggList: PivotAggsConfigDict,
@@ -208,10 +265,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
const searchHandler = (d: Record) => {
const { filterQuery, queryString } = d;
const newSearch = queryString === emptySearch ? defaultSearch : queryString;
- const newSearchQuery =
- filterQuery.match_all && Object.keys(filterQuery.match_all).length === 0
- ? defaultSearch
- : filterQuery;
+ const newSearchQuery = isMatchAllQuery(filterQuery) ? defaultSearch : filterQuery;
setSearchString(newSearch);
setSearchQuery(newSearchQuery);
};
@@ -363,10 +417,10 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
const aggConfigKeys = Object.keys(aggConfig);
const agg = aggConfigKeys[0] as PivotSupportedGroupByAggs;
newGroupByList[aggName] = {
+ ...aggConfig[agg],
agg,
aggName,
dropDownName: '',
- ...aggConfig[agg],
};
});
}
@@ -380,10 +434,10 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
const aggConfigKeys = Object.keys(aggConfig);
const agg = aggConfigKeys[0] as PIVOT_SUPPORTED_AGGS;
newAggList[aggName] = {
+ ...aggConfig[agg],
agg,
aggName,
dropDownName: '',
- ...aggConfig[agg],
};
});
}
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts
index e454ea32d76ed..5cbdf4500e3c3 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts
@@ -4,5 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StepDetailsForm, getDefaultStepDetailsState } from './step_details_form';
+export {
+ applyTransformConfigToDetailsState,
+ getDefaultStepDetailsState,
+ StepDetailsForm,
+} from './step_details_form';
export { StepDetailsSummary } from './step_details_summary';
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
index a01481fde343c..220923f88ed36 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
@@ -49,6 +49,22 @@ export function getDefaultStepDetailsState(): StepDetailsExposedState {
};
}
+export function applyTransformConfigToDetailsState(
+ state: StepDetailsExposedState,
+ transformConfig?: TransformPivotConfig
+): StepDetailsExposedState {
+ // apply the transform configuration to wizard DETAILS state
+ if (transformConfig !== undefined) {
+ const time = transformConfig.sync?.time;
+ if (time !== undefined) {
+ state.continuousModeDateField = time.field;
+ state.continuousModeDelay = time.delay;
+ state.isContinuousModeEnabled = true;
+ }
+ }
+ return state;
+}
+
interface Props {
overrides?: StepDetailsExposedState;
onChange(s: StepDetailsExposedState): void;
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
index 109cf81da6caa..f1861755d9742 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx
@@ -12,16 +12,22 @@ import { EuiSteps, EuiStepStatus } from '@elastic/eui';
import { useKibanaContext } from '../../../../lib/kibana';
-import { getCreateRequestBody } from '../../../../common';
+import { getCreateRequestBody, TransformPivotConfig } from '../../../../common';
import {
+ applyTransformConfigToDefineState,
+ getDefaultStepDefineState,
StepDefineExposedState,
StepDefineForm,
StepDefineSummary,
- getDefaultStepDefineState,
} from '../step_define';
import { getDefaultStepCreateState, StepCreateForm, StepCreateSummary } from '../step_create';
-import { getDefaultStepDetailsState, StepDetailsForm, StepDetailsSummary } from '../step_details';
+import {
+ applyTransformConfigToDetailsState,
+ getDefaultStepDetailsState,
+ StepDetailsForm,
+ StepDetailsSummary,
+} from '../step_details';
import { WizardNav } from '../wizard_nav';
enum KBN_MANAGEMENT_PAGE_CLASSNAME {
@@ -67,17 +73,25 @@ const StepDefine: FC = ({
);
};
-export const Wizard: FC = React.memo(() => {
+interface WizardProps {
+ cloneConfig?: TransformPivotConfig;
+}
+
+export const Wizard: FC = React.memo(({ cloneConfig }) => {
const kibanaContext = useKibanaContext();
// The current WIZARD_STEP
const [currentStep, setCurrentStep] = useState(WIZARD_STEPS.DEFINE);
// The DEFINE state
- const [stepDefineState, setStepDefineState] = useState(getDefaultStepDefineState(kibanaContext));
+ const [stepDefineState, setStepDefineState] = useState(
+ applyTransformConfigToDefineState(getDefaultStepDefineState(kibanaContext), cloneConfig)
+ );
// The DETAILS state
- const [stepDetailsState, setStepDetailsState] = useState(getDefaultStepDetailsState());
+ const [stepDetailsState, setStepDetailsState] = useState(
+ applyTransformConfigToDetailsState(getDefaultStepDetailsState(), cloneConfig)
+ );
const stepDetails =
currentStep === WIZARD_STEPS.DETAILS ? (
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx
new file mode 100644
index 0000000000000..40098ac7ef72a
--- /dev/null
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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, { FC, useContext } from 'react';
+import { useHistory } from 'react-router-dom';
+import { i18n } from '@kbn/i18n';
+import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+
+import {
+ createCapabilityFailureMessage,
+ AuthorizationContext,
+} from '../../../../lib/authorization';
+
+import { CLIENT_BASE_PATH, SECTION_SLUG } from '../../../../constants';
+
+interface CloneActionProps {
+ itemId: string;
+}
+
+export const CloneAction: FC = ({ itemId }) => {
+ const history = useHistory();
+
+ const { canCreateTransform } = useContext(AuthorizationContext).capabilities;
+
+ const buttonCloneText = i18n.translate('xpack.transform.transformList.cloneActionName', {
+ defaultMessage: 'Clone',
+ });
+
+ function clickHandler() {
+ history.push(`${CLIENT_BASE_PATH}/${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`);
+ }
+
+ const cloneButton = (
+
+ {buttonCloneText}
+
+ );
+
+ if (!canCreateTransform) {
+ const content = createCapabilityFailureMessage('canStartStopTransform');
+
+ return (
+
+ {cloneButton}
+
+ );
+ }
+
+ return <>{cloneButton}>;
+};
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx
index 3d847890b2bd5..ef92a5e3859d7 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx
@@ -12,9 +12,10 @@ describe('Transform: Transform List Actions', () => {
test('getActions()', () => {
const actions = getActions({ forceDisable: false });
- expect(actions).toHaveLength(2);
+ expect(actions).toHaveLength(3);
expect(actions[0].isPrimary).toBeTruthy();
expect(typeof actions[0].render).toBe('function');
expect(typeof actions[1].render).toBe('function');
+ expect(typeof actions[2].render).toBe('function');
});
});
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx
index 1773405e36e39..3e3829973e328 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx
@@ -6,6 +6,7 @@
import React from 'react';
import { TransformListRow, TRANSFORM_STATE } from '../../../../common';
+import { CloneAction } from './action_clone';
import { StartAction } from './action_start';
import { StopAction } from './action_stop';
import { DeleteAction } from './action_delete';
@@ -21,6 +22,11 @@ export const getActions = ({ forceDisable }: { forceDisable: boolean }) => {
return ;
},
},
+ {
+ render: (item: TransformListRow) => {
+ return ;
+ },
+ },
{
render: (item: TransformListRow) => {
return ;
diff --git a/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts b/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts
index 0e0b174f28f99..5a2f698b35154 100644
--- a/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts
+++ b/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts
@@ -10,6 +10,7 @@ import { linkToHome } from './links';
export enum BREADCRUMB_SECTION {
MANAGEMENT = 'management',
HOME = 'home',
+ CLONE_TRANSFORM = 'cloneTransform',
CREATE_TRANSFORM = 'createTransform',
}
@@ -27,6 +28,7 @@ class BreadcrumbService {
private breadcrumbs: Breadcrumbs = {
management: [],
home: [],
+ cloneTransform: [],
createTransform: [],
};
@@ -42,6 +44,12 @@ class BreadcrumbService {
href: linkToHome(),
},
];
+ this.breadcrumbs.cloneTransform = [
+ ...this.breadcrumbs.home,
+ {
+ text: textService.breadcrumbs.cloneTransform,
+ },
+ ];
this.breadcrumbs.createTransform = [
...this.breadcrumbs.home,
{
diff --git a/x-pack/legacy/plugins/transform/public/app/services/text/text.ts b/x-pack/legacy/plugins/transform/public/app/services/text/text.ts
index df1b07e171c62..af4aea7e8db4e 100644
--- a/x-pack/legacy/plugins/transform/public/app/services/text/text.ts
+++ b/x-pack/legacy/plugins/transform/public/app/services/text/text.ts
@@ -14,6 +14,9 @@ class TextService {
home: i18n.translate('xpack.transform.home.breadcrumbTitle', {
defaultMessage: 'Transforms',
}),
+ cloneTransform: i18n.translate('xpack.transform.cloneTransform.breadcrumbTitle', {
+ defaultMessage: 'Clone transform',
+ }),
createTransform: i18n.translate('xpack.transform.createTransform.breadcrumbTitle', {
defaultMessage: 'Create transform',
}),