Skip to content

Commit

Permalink
Add profiles overview/edit pages (#17769)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxiadlovskii committed Jan 10, 2024
1 parent 84001dc commit 449e99f
Show file tree
Hide file tree
Showing 52 changed files with 2,330 additions and 62 deletions.
12 changes: 12 additions & 0 deletions changelog/unreleased/issue-17746.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type = "a"
message = "Add index set field type profiles overview and edit page"

pulls = ["17775"]
issues=["17746"]

details.user = """
Before this change, it was possible to create and manage custom field type mappings
of existing index sets. For every new index set that is created,
these steps have to be repeated though. This change gives an option to bundle up
custom field types into profiles.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import { render, screen, fireEvent, act, waitFor } from 'wrappedTestingLibrary';
import selectEvent from 'react-select-event';

import asMock from 'helpers/mocking/AsMock';
import { loadViewsPlugin, unloadViewsPlugin } from 'views/test/testViewsPlugin';
import useFieldTypesForMappings from 'views/logic/fieldactions/ChangeFieldType/hooks/useFieldTypesForMappings';
import useFieldTypes from 'views/logic/fieldtypes/useFieldTypes';
import CreateProfile from 'components/indices/IndexSetFieldTypeProfiles/CreateProfile';
import useProfileMutations from 'components/indices/IndexSetFieldTypeProfiles/hooks/useProfileMutations';
import { simpleFields } from 'fixtures/fields';

const renderCreateNewProfile = () => render(
<CreateProfile />,
);

jest.mock('components/indices/IndexSetFieldTypeProfiles/hooks/useProfileMutations', () => jest.fn());
jest.mock('views/logic/fieldactions/ChangeFieldType/hooks/useFieldTypesForMappings', () => jest.fn());

jest.mock('views/logic/fieldtypes/useFieldTypes', () => jest.fn());

const selectItem = async (select: HTMLElement, option: string | RegExp) => {
selectEvent.openMenu(select);

return selectEvent.select(select, option);
};

describe('IndexSetFieldTypesList', () => {
const createMock = jest.fn(() => Promise.resolve());
const editMock = jest.fn(() => Promise.resolve());

beforeAll(loadViewsPlugin);

afterAll(unloadViewsPlugin);

beforeEach(() => {
asMock(useFieldTypesForMappings).mockReturnValue({
data: {
fieldTypes: {
string: 'String type',
int: 'Number(int)',
bool: 'Boolean',
ip: 'IP',
date: 'Date',
},
},
isLoading: false,
});

asMock(useProfileMutations).mockReturnValue(({
editProfile: editMock,
isEditLoading: false,
createProfile: createMock,
isCreateLoading: false,
isLoading: false,
}));

asMock(useFieldTypes).mockImplementation(() => (
{ data: simpleFields().toArray(), refetch: jest.fn() }
));
});

it('Run createProfile with form data', async () => {
renderCreateNewProfile();

const name = await screen.findByRole('textbox', {
name: /name/i,
hidden: true,
});
const description = await screen.findByRole('textbox', {
name: /description/i,
hidden: true,
});
const addMappingButton = await screen.findByRole('button', { name: /add mapping/i });

// eslint-disable-next-line testing-library/no-unnecessary-act
await act(async () => {
fireEvent.click(addMappingButton);
});

const fieldFirst = await screen.findByLabelText(/select customFieldMappings.0.field/i);
const typeFirst = await screen.findByLabelText(/select customFieldMappings.0.type/i);
const fieldSecond = await screen.findByLabelText(/select customFieldMappings.1.field/i);
const typeSecond = await screen.findByLabelText(/select customFieldMappings.1.type/i);
const submitButton = await screen.findByTitle(/create profile/i);

// eslint-disable-next-line testing-library/no-unnecessary-act
await act(async () => {
fireEvent.change(name, { target: { value: 'Profile new' } });
fireEvent.change(description, { target: { value: 'Profile description' } });
await selectItem(fieldFirst, 'date');
await selectItem(typeFirst, 'String type');
await selectItem(fieldSecond, 'http_method');
await selectItem(typeSecond, 'String type');
await waitFor(() => expect(submitButton.hasAttribute('disabled')).toBe(false));
fireEvent.click(submitButton);
});

expect(createMock).toHaveBeenCalledWith({
name: 'Profile new',
description: 'Profile description',
customFieldMappings: [
{ field: 'date', type: 'string' },
{ field: 'http_method', type: 'string' },
],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import React, { useMemo, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
import { getPathnameWithoutId } from 'util/URLUtils';
import useLocation from 'routing/useLocation';
import ProfileForm from 'components/indices/IndexSetFieldTypeProfiles/ProfileForm';
import type { IndexSetFieldTypeProfile } from 'components/indices/IndexSetFieldTypeProfiles/types';
import useProfileMutations from 'components/indices/IndexSetFieldTypeProfiles/hooks/useProfileMutations';
import Routes from 'routing/Routes';

const CreateProfile = () => {
const sendTelemetry = useSendTelemetry();
const { pathname } = useLocation();
const navigate = useNavigate();
const { createProfile } = useProfileMutations();
const telemetryPathName = useMemo(() => getPathnameWithoutId(pathname), [pathname]);

const onSubmit = useCallback((profile: IndexSetFieldTypeProfile) => {
createProfile(profile).then(() => {
sendTelemetry(TELEMETRY_EVENT_TYPE.INDEX_SET_FIELD_TYPE_PROFILE.CREATED, {
app_pathname: telemetryPathName,
app_action_value: { mappingsQuantity: profile?.customFieldMappings?.length },
});

navigate(Routes.SYSTEM.INDICES.FIELD_TYPE_PROFILES.OVERVIEW);
});
}, [createProfile, navigate, sendTelemetry, telemetryPathName]);

useEffect(() => {
sendTelemetry(TELEMETRY_EVENT_TYPE.INDEX_SET_FIELD_TYPE_PROFILE.NEW_OPENED, { app_pathname: telemetryPathName, app_action_value: 'create-new-index-set-field-type-profile-opened' });
}, [sendTelemetry, telemetryPathName]);

const onCancel = useCallback(() => {
sendTelemetry(TELEMETRY_EVENT_TYPE.INDEX_SET_FIELD_TYPE_PROFILE.NEW_CANCELED, { app_pathname: telemetryPathName, app_action_value: 'create-new-index-set-field-type-profile-canceled' });
navigate(Routes.SYSTEM.INDICES.FIELD_TYPE_PROFILES.OVERVIEW);
}, [navigate, sendTelemetry, telemetryPathName]);

return (
<ProfileForm onCancel={onCancel} submitButtonText="Create profile" submitLoadingText="Creating profile..." onSubmit={onSubmit} />
);
};

export default CreateProfile;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/

import * as React from 'react';

import { Button } from 'components/bootstrap';
import Routes from 'routing/Routes';
import { LinkContainer } from 'components/common/router';

const CreateProfileButton = () => (
<LinkContainer to={Routes.SYSTEM.INDICES.FIELD_TYPE_PROFILES.CREATE}>
<Button bsStyle="success">Create profile</Button>
</LinkContainer>
);

export default CreateProfileButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import React from 'react';
import { styled } from 'styled-components';

import type { CustomFieldMapping } from 'components/indices/IndexSetFieldTypeProfiles/types';
import type { FieldTypes } from 'views/logic/fieldactions/ChangeFieldType/types';

const Item = styled.div`
display: flex;
gap: 5px;
flex-wrap: wrap;
`;

const List = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
`;
const CustomFieldTypesList = ({ list, fieldTypes }: { list: Array<CustomFieldMapping>, fieldTypes: FieldTypes }) => (
<List>
{list.map(({ field, type }) => (
<Item key={field}>
<b>{field}:</b><i>{fieldTypes[type]}</i>
</Item>
))}
</List>
);

export default CustomFieldTypesList;
Loading

0 comments on commit 449e99f

Please sign in to comment.