Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
* 2.0.
*/

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

import { useRedirectPath } from '../../../../hooks/redirect_path';
import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs';
import { ComponentTemplateDeserialized } from '../../shared_imports';
import { useComponentTemplatesContext } from '../../component_templates_context';
import { ComponentTemplateForm } from '../component_template_form';
import { useStepFromQueryString } from '../use_step_from_query_string';
import { useDatastreamsRollover } from '../component_template_datastreams_rollover/use_datastreams_rollover';
import { MANAGED_BY_FLEET } from '../../constants';

interface Props {
/**
Expand All @@ -28,8 +32,35 @@ export const ComponentTemplateCreate: React.FunctionComponent<RouteComponentProp
}) => {
const [isSaving, setIsSaving] = useState<boolean>(false);
const [saveError, setSaveError] = useState<any>(null);
const redirectTo = useRedirectPath(history);

const { activeStep: defaultActiveStep, updateStep } = useStepFromQueryString(history);

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

const defaultValue = useMemo(() => {
if (sourceComponentTemplate) {
return sourceComponentTemplate;
}

const name = locationSearchParams.get('name') ?? '';
const managedBy = locationSearchParams.get('managed_by');

return {
name,
template: {},
_meta: managedBy ? { managed_by: managedBy } : {},
_kbnMeta: {
usedBy: [],
isManaged: false,
},
};
}, [locationSearchParams, sourceComponentTemplate]);

const { api } = useComponentTemplatesContext();
const { showDatastreamRolloverModal } = useDatastreamsRollover();

const onSave = async (componentTemplate: ComponentTemplateDeserialized) => {
const { name } = componentTemplate;
Expand All @@ -45,10 +76,11 @@ export const ComponentTemplateCreate: React.FunctionComponent<RouteComponentProp
setSaveError(error);
return;
}
if (componentTemplate._meta?.managed_by === MANAGED_BY_FLEET) {
await showDatastreamRolloverModal(componentTemplate.name);
}

history.push({
pathname: encodeURI(`/component_templates/${encodeURIComponent(name)}`),
});
redirectTo(encodeURI(`/component_templates/${encodeURIComponent(name)}`));
};

const clearSaveError = () => {
Expand Down Expand Up @@ -88,7 +120,9 @@ export const ComponentTemplateCreate: React.FunctionComponent<RouteComponentProp
<EuiSpacer size="l" />

<ComponentTemplateForm
defaultValue={sourceComponentTemplate}
defaultActiveWizardSection={defaultActiveStep}
onStepChange={updateStep}
defaultValue={defaultValue}
onSave={onSave}
isSaving={isSaving}
saveError={saveError}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 { useComponentTemplatesContext } from '../../component_templates_context';

import { useDatastreamsRollover } from './use_datastreams_rollover';

jest.mock('../../component_templates_context');

describe('useStepFromQueryString', () => {
beforeEach(() => {
jest.mocked(useComponentTemplatesContext).mockReturnValue({
api: {
getComponentTemplateDatastreams: jest.fn(),
postDataStreamMappingsFromTemplate: jest.fn(),
},
overlays: { openModal: jest.fn() } as any,
} as any);
});
it('should do nothing if there no impacted data_streams', async () => {
jest
.mocked(useComponentTemplatesContext().api.getComponentTemplateDatastreams)
.mockResolvedValue({ data: { data_streams: [] }, error: undefined });

const {
result: {
current: { showDatastreamRolloverModal },
},
} = renderHook(() => useDatastreamsRollover());

await showDatastreamRolloverModal('logs-test.data@custom');
});

it('should try to update mappings if there is impacted data_streams', async () => {
const { api, overlays } = jest.mocked(useComponentTemplatesContext());

api.getComponentTemplateDatastreams.mockResolvedValue({
data: { data_streams: ['logs-test.data-default'] },
error: undefined,
});

api.postDataStreamMappingsFromTemplate.mockResolvedValue({
error: undefined,
data: { data_streams: [] },
});

jest
.mocked(useComponentTemplatesContext().overlays.openModal)
.mockReturnValue({ onClose: jest.fn() } as any);

const {
result: {
current: { showDatastreamRolloverModal },
},
} = renderHook(() => useDatastreamsRollover());

await showDatastreamRolloverModal('logs-test.data@custom');

expect(api.postDataStreamMappingsFromTemplate).toBeCalledTimes(1);
expect(overlays.openModal).not.toBeCalled();
});

it('should show datastream rollover modal if there is an error when updating mappings', async () => {
const { api, overlays } = jest.mocked(useComponentTemplatesContext());

api.getComponentTemplateDatastreams.mockResolvedValue({
data: { data_streams: ['logs-test.data-default'] },
error: undefined,
});

api.postDataStreamMappingsFromTemplate.mockResolvedValue({
error: new Error('test'),
data: { data_streams: [] },
});

jest
.mocked(useComponentTemplatesContext().overlays.openModal)
.mockReturnValue({ onClose: jest.fn() } as any);

const {
result: {
current: { showDatastreamRolloverModal },
},
} = renderHook(() => useDatastreamsRollover());

await showDatastreamRolloverModal('logs-test.data@custom');
expect(overlays.openModal).toBeCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 React, { useCallback } from 'react';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';

import { useComponentTemplatesContext } from '../../component_templates_context';
import { MappingsDatastreamRolloverModal } from './mappings_datastreams_rollover_modal';

export const test = {};

export function useDatastreamsRollover() {
const { api, overlays } = useComponentTemplatesContext();

const showDatastreamRolloverModal = useCallback(
async (componentTemplateName: string) => {
const { data: dataStreamResponse } = await api.getComponentTemplateDatastreams(
componentTemplateName
);
const dataStreams = dataStreamResponse?.data_streams ?? [];

const dataStreamsToRollover: string[] = [];
for (const dataStream of dataStreams) {
try {
const { error: applyMappingError } = await api.postDataStreamMappingsFromTemplate(
dataStream
);
if (applyMappingError) {
throw applyMappingError;
}
} catch (err) {
dataStreamsToRollover.push(dataStream);
}
}

if (dataStreamsToRollover.length) {
const ref = overlays.openModal(
toMountPoint(
<MappingsDatastreamRolloverModal
componentTemplatename={componentTemplateName}
dataStreams={dataStreamsToRollover}
api={api}
onClose={() => {
ref.close();
}}
/>
)
);

await ref.onClose;
}
},
[api, overlays]
);

return {
showDatastreamRolloverModal,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
* 2.0.
*/

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

import { breadcrumbService, IndexManagementBreadcrumb } from '../../../../services/breadcrumbs';
import { useComponentTemplatesContext } from '../../component_templates_context';
Expand All @@ -22,47 +20,23 @@ import {
Error,
} from '../../shared_imports';
import { ComponentTemplateForm } from '../component_template_form';
import type { WizardSection } from '../component_template_form';
import { useRedirectPath } from '../../../../hooks/redirect_path';
import { MANAGED_BY_FLEET } from '../../constants';

import { MappingsDatastreamRolloverModal } from './mappings_datastreams_rollover_modal';
import { useStepFromQueryString } from '../use_step_from_query_string';
import { useDatastreamsRollover } from '../component_template_datastreams_rollover/use_datastreams_rollover';

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, overlays } = useComponentTemplatesContext();
const { api } = useComponentTemplatesContext();
const { activeStep: defaultActiveStep, updateStep } = useStepFromQueryString(history);
const redirectTo = useRedirectPath(history);

Expand All @@ -75,6 +49,8 @@ export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<
const { data: dataStreamResponse } = api.useLoadComponentTemplatesDatastream(decodedName);
const dataStreams = useMemo(() => dataStreamResponse?.data_streams ?? [], [dataStreamResponse]);

const { showDatastreamRolloverModal } = useDatastreamsRollover();

useEffect(() => {
breadcrumbService.setBreadcrumbs(IndexManagementBreadcrumb.componentTemplateEdit);
}, []);
Expand All @@ -92,37 +68,8 @@ export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<
return;
}

if (updatedComponentTemplate._meta?.managed_by === MANAGED_BY_FLEET && dataStreams.length) {
const dataStreamsToRollover: string[] = [];
for (const dataStream of dataStreams) {
try {
const { error: applyMappingError } = await api.postDataStreamMappingsFromTemplate(
dataStream
);
if (applyMappingError) {
throw applyMappingError;
}
} catch (err) {
dataStreamsToRollover.push(dataStream);
}
}

if (dataStreamsToRollover.length) {
const ref = overlays.openModal(
toMountPoint(
<MappingsDatastreamRolloverModal
componentTemplatename={updatedComponentTemplate.name}
dataStreams={dataStreamsToRollover}
api={api}
onClose={() => {
ref.close();
}}
/>
)
);

await ref.onClose;
}
if (updatedComponentTemplate._meta?.managed_by === MANAGED_BY_FLEET) {
await showDatastreamRolloverModal(updatedComponentTemplate.name);
}
redirectTo({
pathname: encodeURI(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

describe('useStepFromQueryString', () => {
it('should return undefined if no step is set in the url', () => {
Expand Down
Loading