Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function FormWizard<T extends object = { [key: string]: any }, S extends
isSaving,
onSave,
onChange,
onStepChange,
children,
rightContentNav,
}: Props<T, S>) {
Expand All @@ -41,6 +42,7 @@ export function FormWizard<T extends object = { [key: string]: any }, S extends
isEditing={isEditing}
onSave={onSave}
onChange={onChange}
onStepChange={onStepChange}
defaultActiveStep={defaultActiveStep}
>
<FormWizardConsumer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface Props<T extends object> {
defaultActiveStep?: number;
defaultValue?: HookProps<T>['defaultValue'];
onChange?: HookProps<T>['onChange'];
onStepChange?: (id: string) => void;
}

interface State {
Expand Down Expand Up @@ -48,7 +49,7 @@ const formWizardContext = createContext<Context>({} as Context);

export const FormWizardProvider = WithMultiContent<Props<any>>(function FormWizardProvider<
T extends object = { [key: string]: any }
>({ children, defaultActiveStep = 0, isEditing, onSave }: Props<T>) {
>({ children, defaultActiveStep = 0, isEditing, onSave, onStepChange }: Props<T>) {
const { getData, validate, validation } = useMultiContentContext<T>();

const [state, setState] = useState<State>({
Expand Down Expand Up @@ -135,8 +136,12 @@ export const FormWizardProvider = WithMultiContent<Props<any>>(function FormWiza

return nextState;
});
// Trigger onStepChange
if (onStepChange) {
onStepChange(Object.values(state.steps)[getStepIndex(stepId)]?.id);
}
},
[getStepIndex, validate, onSave, getData, lastStep]
[getStepIndex, validate, onSave, onStepChange, getData, lastStep, state.steps]
);

const value: Context = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface AppDependencies {
fatalErrors: FatalErrorsStart;
getUrlForApp: ApplicationStart['getUrlForApp'];
executionContext: ExecutionContextStart;
application: ApplicationStart;
};
plugins: {
usageCollection: UsageCollectionSetup;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ describe('<ComponentTemplateEdit />', () => {
expect(nameInput.props().disabled).toEqual(true);
});

it('should allow to go directly to a step', async () => {
await act(async () => {
testBed = await setup(httpSetup, '?step=mappings');
});

testBed.component.update();

expect(testBed.exists('mappingsEditor')).toBe(true);
});

describe('form payload', () => {
it('should send the correct payload with changed values', async () => {
const { actions, component, form } = testBed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@ export type ComponentTemplateEditTestBed = TestBed<ComponentTemplateFormTestSubj
actions: ReturnType<typeof getFormActions>;
};

const testBedConfig: AsyncTestBedConfig = {
memoryRouter: {
initialEntries: [`${BASE_PATH}/edit_component_template/comp-1`],
componentRoutePath: `${BASE_PATH}/edit_component_template/:name`,
},
doMountAsync: true,
};
export const setup = async (
httpSetup: HttpSetup,
queryParams: string = ''
): Promise<ComponentTemplateEditTestBed> => {
const testBedConfig: AsyncTestBedConfig = {
memoryRouter: {
initialEntries: [`${BASE_PATH}/edit_component_template/comp-1${queryParams}`],
componentRoutePath: `${BASE_PATH}/edit_component_template/:name`,
},
doMountAsync: true,
};

export const setup = async (httpSetup: HttpSetup): Promise<ComponentTemplateEditTestBed> => {
const initTestBed = registerTestBed(
WithAppDependencies(ComponentTemplateEdit, httpSetup),
testBedConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,6 @@ export type ComponentTemplateFormTestSubjects =
| 'stepReview.requestTab'
| 'versionField'
| 'aliasesEditor'
| 'mappingsEditor'
| 'settingsEditor'
| 'versionField.input';
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import { EmptyPrompt } from './empty_prompt';
import { ComponentTable } from './table';
import { ComponentTemplatesDeleteModal } from './delete_modal';
import { useRedirectPath } from '../../../hooks/redirect_path';

interface Props {
componentTemplateName?: string;
Expand All @@ -45,16 +46,17 @@ export const ComponentTemplateList: React.FunctionComponent<Props> = ({
const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } =
useGlobalFlyout();
const { api, trackMetric, documentation } = useComponentTemplatesContext();
const redirectTo = useRedirectPath(history);

const { data, isLoading, error, resendRequest } = api.useLoadComponentTemplates();

const [componentTemplatesToDelete, setComponentTemplatesToDelete] = useState<string[]>([]);

const goToComponentTemplateList = useCallback(() => {
return history.push({
return redirectTo({
pathname: 'component_templates',
});
}, [history]);
}, [redirectTo]);

const goToEditComponentTemplate = useCallback(
(name: string) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { renderHook } from '@testing-library/react-hooks';
import { createMemoryHistory } from 'history';
import { useStepFromQueryString } from './component_template_edit';

describe('useStepFromQueryString', () => {
it('should return undefined if no step is set in the url', () => {
const history = createMemoryHistory();
history.push('/app/management/data/index_management/edit_component_template');

const {
result: {
current: { activeStep },
},
} = renderHook(() => useStepFromQueryString(history));

expect(activeStep).not.toBeDefined();
});

it('should return the step if set in the url', () => {
const history = createMemoryHistory();
history.push('/app/management/data/index_management/edit_component_template?step=mappings');

const {
result: {
current: { activeStep },
},
} = renderHook(() => useStepFromQueryString(history));

expect(activeStep).toBe('mappings');
});

it('should not update history on step change if no step is set in the url', () => {
const history = createMemoryHistory();
history.push('/app/management/data/index_management/edit_component_template');

const {
result: {
current: { updateStep },
},
} = renderHook(() => useStepFromQueryString(history));

updateStep('aliases');

expect(history.location.search).toBe('');
});

it('should update history on step change if a step is set in the url', () => {
const history = createMemoryHistory();
history.push('/app/management/data/index_management/edit_component_template?step=mappings');

const {
result: {
current: { updateStep },
},
} = renderHook(() => useStepFromQueryString(history));

updateStep('aliases');
expect(history.location.search).toBe('?step=aliases');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
* 2.0.
*/

import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiPageContentBody, EuiPageHeader, EuiSpacer } from '@elastic/eui';
import { History } from 'history';

import { useComponentTemplatesContext } from '../../component_templates_context';
import {
Expand All @@ -19,18 +20,46 @@ import {
Error,
} from '../../shared_imports';
import { ComponentTemplateForm } from '../component_template_form';
import type { WizardSection } from '../component_template_form';
import { useRedirectPath } from '../../../../hooks/redirect_path';

interface MatchParams {
name: string;
}

export function useStepFromQueryString(history: History) {
const activeStep = useMemo(() => {
const params = new URLSearchParams(history.location.search);
if (params.has('step')) {
return params.get('step') as WizardSection;
}
}, [history.location.search]);

const updateStep = useCallback(
(stepId: string) => {
const params = new URLSearchParams(history.location.search);
if (params.has('step')) {
params.set('step', stepId);
history.push({
search: params.toString(),
});
}
},
[history]
);

return { activeStep, updateStep };
}

export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<MatchParams>> = ({
match: {
params: { name },
},
history,
}) => {
const { api, breadcrumbs } = useComponentTemplatesContext();
const { activeStep: defaultActiveStep, updateStep } = useStepFromQueryString(history);
const redirectTo = useRedirectPath(history);

const [isSaving, setIsSaving] = useState<boolean>(false);
const [saveError, setSaveError] = useState<any>(null);
Expand All @@ -56,7 +85,7 @@ export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<
return;
}

history.push({
redirectTo({
pathname: encodeURI(
`/component_templates/${encodeURIComponent(updatedComponentTemplate.name)}`
),
Expand Down Expand Up @@ -112,6 +141,8 @@ export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<

<ComponentTemplateForm
defaultValue={componentTemplate!}
defaultActiveWizardSection={defaultActiveStep}
onStepChange={updateStep}
onSave={onSave}
isSaving={isSaving}
saveError={saveError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiSpacer, EuiCallOut } from '@elastic/eui';
Expand All @@ -25,21 +25,23 @@ import { StepLogisticsContainer, StepReviewContainer } from './steps';
const { stripEmptyFields } = serializers;
const { FormWizard, FormWizardStep } = Forms;

export interface WizardContent extends CommonWizardSteps {
logistics: Omit<ComponentTemplateDeserialized, '_kbnMeta' | 'template'>;
}

export type WizardSection = keyof WizardContent | 'review';

interface Props {
onSave: (componentTemplate: ComponentTemplateDeserialized) => void;
clearSaveError: () => void;
isSaving: boolean;
saveError: any;
defaultValue?: ComponentTemplateDeserialized;
isEditing?: boolean;
defaultActiveWizardSection?: WizardSection;
onStepChange?: (stepId: string) => void;
}

export interface WizardContent extends CommonWizardSteps {
logistics: Omit<ComponentTemplateDeserialized, '_kbnMeta' | 'template'>;
}

export type WizardSection = keyof WizardContent | 'review';

const wizardSections: { [id: string]: { id: WizardSection; label: string } } = {
logistics: {
id: 'logistics',
Expand Down Expand Up @@ -87,7 +89,9 @@ export const ComponentTemplateForm = ({
isSaving,
saveError,
clearSaveError,
defaultActiveWizardSection,
onSave,
onStepChange,
}: Props) => {
const {
template: { settings, mappings, aliases },
Expand Down Expand Up @@ -194,6 +198,17 @@ export const ComponentTemplateForm = ({
[buildComponentTemplateObject, defaultValue, onSave, clearSaveError]
);

const defaultActiveStepIndex = useMemo(
() =>
Math.max(
defaultActiveWizardSection
? Object.keys(wizardSections).indexOf(defaultActiveWizardSection)
: 0,
0
),
[defaultActiveWizardSection]
);

return (
<FormWizard<WizardContent>
defaultValue={wizardDefaultValue}
Expand All @@ -202,6 +217,8 @@ export const ComponentTemplateForm = ({
isSaving={isSaving}
apiError={apiError}
texts={i18nTexts}
defaultActiveStep={defaultActiveStepIndex}
onStepChange={onStepChange}
>
<FormWizardStep
id={wizardSections.logistics.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*/

export { ComponentTemplateForm } from './component_template_form';
export type { WizardSection } from './component_template_form';
Loading