Skip to content

Commit

Permalink
[Ingest Pipeline] Provide url params to use ingestPipeline UI from Fl…
Browse files Browse the repository at this point in the history
…eet (#134776)
  • Loading branch information
nchaulet authored Jun 21, 2022
1 parent d8558fc commit 04c8fb9
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ export type PipelinesCreateTestBed = TestBed<PipelineFormTestSubjects> & {
actions: ReturnType<typeof getFormActions>;
};

const testBedConfig: AsyncTestBedConfig = {
memoryRouter: {
initialEntries: [getCreatePath()],
componentRoutePath: ROUTES.create,
},
doMountAsync: true,
};
export const setup = async (
httpSetup: HttpSetup,
queryParams: string = ''
): Promise<PipelinesCreateTestBed> => {
const testBedConfig: AsyncTestBedConfig = {
memoryRouter: {
initialEntries: [`${getCreatePath()}${queryParams}`],
componentRoutePath: ROUTES.create,
},
doMountAsync: true,
};

export const setup = async (httpSetup: HttpSetup): Promise<PipelinesCreateTestBed> => {
const initTestBed = registerTestBed(
WithAppDependencies(PipelinesCreate, httpSetup),
testBedConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ describe('<PipelinesCreate />', () => {
expect(find('apiRequestFlyout.apiRequestFlyoutTitle').text()).toBe('Request');
});

test('should allow to prepopulate the name field', async () => {
await act(async () => {
testBed = await setup(httpSetup, '?name=test-pipeline');
});

testBed.component.update();

expect(testBed.exists('nameField.input')).toBe(true);
expect(testBed.find('nameField.input').props().disabled).toBe(true);
expect(testBed.find('nameField.input').props().value).toBe('test-pipeline');
});

describe('form validation', () => {
test('should prevent form submission if required fields are missing', async () => {
const { form, actions, component, find } = testBed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export interface PipelineFormProps {
saveError: any;
defaultValue?: Pipeline;
isEditing?: boolean;
// That fields is used to disable the name field when creating a pipeline with the name prepopulated
canEditName?: boolean;
}

const defaultFormValue: Pipeline = Object.freeze({
Expand All @@ -43,6 +45,7 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
saveError,
isEditing,
onCancel,
canEditName,
}) => {
const [isRequestVisible, setIsRequestVisible] = useState<boolean>(false);

Expand Down Expand Up @@ -129,6 +132,7 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
onProcessorsUpdate={onProcessorsChangeHandler}
hasVersion={Boolean(defaultValue.version)}
isEditing={isEditing}
canEditName={canEditName}
/>

{/* Form submission */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface Props {
hasVersion: boolean;
onEditorFlyoutOpen: () => void;
isEditing?: boolean;
canEditName?: boolean;
}

const UseField = getUseField({ component: Field });
Expand All @@ -41,6 +42,7 @@ export const PipelineFormFields: React.FunctionComponent<Props> = ({
isEditing,
hasVersion,
onEditorFlyoutOpen,
canEditName,
}) => {
const [isVersionVisible, setIsVersionVisible] = useState<boolean>(hasVersion);

Expand Down Expand Up @@ -74,7 +76,7 @@ export const PipelineFormFields: React.FunctionComponent<Props> = ({
path="name"
componentProps={{
['data-test-subj']: 'nameField',
euiFieldProps: { disabled: Boolean(isEditing) },
euiFieldProps: { disabled: canEditName === false || Boolean(isEditing) },
}}
/>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export { useRedirectPath as useRedirectToPathOrRedirectPath } from './redirect_path';
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { useRedirectPath } from './redirect_path';
import { useKibana } from '../../shared_imports';

const mockedUseKibana = useKibana as jest.MockedFunction<typeof useKibana>;
jest.mock('../../shared_imports');

describe('useRedirectPath', () => {
const mockedNavigateToUrl = jest.fn();
beforeEach(() => {
mockedNavigateToUrl.mockReset();
mockedUseKibana.mockReturnValue({
services: {
application: {
navigateToUrl: mockedNavigateToUrl,
},
},
} as any);
});
it('should redirect to redirect path if a redirect path is specified in the url', () => {
const history = createMemoryHistory();
history.push(
'/app/management/ingest/ingest_pipelines/create?name=logs-system.syslog@custom&redirect_path=/test-redirect-path'
);

const {
result: { current: redirectToPathOrRedirectPath },
} = renderHook(() => useRedirectPath(history));

redirectToPathOrRedirectPath('/test');

expect(mockedNavigateToUrl).toBeCalledWith('/test-redirect-path');
});

it('should redirect to the provided path if no redirect path is specified in the url', () => {
const history = createMemoryHistory();
history.push('/app/management/ingest/ingest_pipelines/create');

const {
result: { current: redirectToPathOrRedirectPath },
} = renderHook(() => useRedirectPath(history));

redirectToPathOrRedirectPath('/test');

expect(mockedNavigateToUrl).not.toBeCalled();
expect(history.location.pathname).toBe('/test');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { History } from 'history';
import { useCallback, useMemo } from 'react';

import { useKibana } from '../../shared_imports';

/**
* This hook allow to redirect to the provided path or using redirect_path if it's provided in the query params.
*/
export function useRedirectPath(history: History) {
const { services } = useKibana();

const redirectPath = useMemo(() => {
const locationSearchParams = new URLSearchParams(history.location.search);

return locationSearchParams.get('redirect_path');
}, [history.location.search]);

return useCallback(
(path: string) => {
if (redirectPath) {
services.application.navigateToUrl(redirectPath);
} else {
history.push(path);
}
},
[redirectPath, services.application, history]
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getListPath } from '../../services/navigation';
import { Pipeline } from '../../../../common/types';
import { useKibana } from '../../../shared_imports';
import { PipelineForm } from '../../components';
import { useRedirectToPathOrRedirectPath } from '../../hooks';

interface Props {
/**
Expand All @@ -26,15 +27,48 @@ interface LocationState {
sourcePipeline?: Pipeline;
}

function useFormDefaultValue(sourcePipeline?: Pipeline) {
const history = useHistory<LocationState>();

const locationSearchParams = useMemo(() => {
return new URLSearchParams(history.location.search);
}, [history.location.search]);

const formDefaultValue = useMemo(() => {
if (sourcePipeline) {
return sourcePipeline;
}

if (history.location.state?.sourcePipeline) {
return history.location.state.sourcePipeline;
}

if (locationSearchParams.has('name')) {
return {
name: locationSearchParams.get('name') as string,
description: '',
processors: [],
on_failure: [],
};
}
}, [sourcePipeline, history, locationSearchParams]);

return { formDefaultValue, canEditName: !locationSearchParams.has('name') };
}

export const PipelinesCreate: React.FunctionComponent<RouteComponentProps & Props> = ({
sourcePipeline,
}) => {
const history = useHistory<LocationState>();

const { services } = useKibana();

const [isSaving, setIsSaving] = useState<boolean>(false);
const [saveError, setSaveError] = useState<any>(null);

const { formDefaultValue, canEditName } = useFormDefaultValue(sourcePipeline);
const redirectToPathOrRedirectPath = useRedirectToPathOrRedirectPath(history);

const onSave = async (pipeline: Pipeline) => {
setIsSaving(true);
setSaveError(null);
Expand All @@ -48,27 +82,15 @@ export const PipelinesCreate: React.FunctionComponent<RouteComponentProps & Prop
return;
}

history.push(getListPath({ inspectedPipelineName: pipeline.name }));
redirectToPathOrRedirectPath(getListPath({ inspectedPipelineName: pipeline.name }));
};

const onCancel = () => {
history.push(getListPath());
};
const onCancel = () => redirectToPathOrRedirectPath(getListPath());

useEffect(() => {
services.breadcrumbs.setBreadcrumbs('create');
}, [services]);

const formDefaultValue = useMemo(() => {
if (sourcePipeline) {
return sourcePipeline;
}

if (history.location.state?.sourcePipeline) {
return history.location.state.sourcePipeline;
}
}, [sourcePipeline, history]);

return (
<>
<EuiPageHeader
Expand Down Expand Up @@ -102,6 +124,7 @@ export const PipelinesCreate: React.FunctionComponent<RouteComponentProps & Prop

<PipelineForm
defaultValue={formDefaultValue}
canEditName={canEditName}
onSave={onSave}
onCancel={onCancel}
isSaving={isSaving}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useKibana, SectionLoading, attemptToURIDecode } from '../../../shared_i

import { getListPath } from '../../services/navigation';
import { PipelineForm } from '../../components';
import { useRedirectToPathOrRedirectPath } from '../../hooks';

interface MatchParams {
name: string;
Expand All @@ -37,6 +38,7 @@ export const PipelinesEdit: React.FunctionComponent<RouteComponentProps<MatchPar

const [isSaving, setIsSaving] = useState<boolean>(false);
const [saveError, setSaveError] = useState<any>(null);
const redirectToPathOrRedirectPath = useRedirectToPathOrRedirectPath(history);

const decodedPipelineName = attemptToURIDecode(name)!;

Expand All @@ -60,11 +62,11 @@ export const PipelinesEdit: React.FunctionComponent<RouteComponentProps<MatchPar
return;
}

history.push(getListPath({ inspectedPipelineName: updatedPipeline.name }));
redirectToPathOrRedirectPath(getListPath({ inspectedPipelineName: updatedPipeline.name }));
};

const onCancel = () => {
history.push(getListPath());
redirectToPathOrRedirectPath(getListPath());
};

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { PipelineTable } from './table';
import { PipelineDetailsFlyout } from './details_flyout';
import { PipelineNotFoundFlyout } from './not_found_flyout';
import { PipelineDeleteModal } from './delete_modal';
import { useRedirectToPathOrRedirectPath } from '../../hooks';

const getPipelineNameFromLocation = (location: Location) => {
const { pipeline } = parse(location.search.substring(1));
Expand All @@ -49,6 +50,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({
const [pipelinesToDelete, setPipelinesToDelete] = useState<string[]>([]);

const { data, isLoading, error, resendRequest } = services.api.useLoadPipelines();
const redirectToPathOrRedirectPath = useRedirectToPathOrRedirectPath(history);

// Track component loaded
useEffect(() => {
Expand All @@ -74,7 +76,7 @@ export const PipelinesList: React.FunctionComponent<RouteComponentProps> = ({

const goHome = () => {
setShowFlyout(false);
history.push(getListPath());
redirectToPathOrRedirectPath(getListPath());
};

if (error) {
Expand Down

0 comments on commit 04c8fb9

Please sign in to comment.