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 @@ -11,6 +11,7 @@ export enum APIRoutes {
QUERY_RULES_SETS = '/internal/search_query_rules/query_rules_sets',
QUERY_RULES_QUERY_RULE_FETCH = '/internal/search_query_rules/ruleset/{ruleset_id}/rule/{rule_id}',
QUERY_RULES_RULESET_ID = '/internal/search_query_rules/ruleset/{ruleset_id}',
QUERY_RULES_RULESET_EXISTS = '/internal/search_query_rules/ruleset/{rulesetId}/exists',
FETCH_INDICES = '/internal/search_query_rules/indices',
FETCH_DOCUMENT = '/internal/search_query_rules/document/{indexName}/{documentId}',
GENERATE_RULE_ID = '/internal/search_query_rules/ruleset/{rulesetId}/generate_rule_id',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import dedent from 'dedent';
export const QUERY_RULES_SETS_QUERY_KEY = 'query-rules-sets-fetch';
export const QUERY_RULES_QUERY_RULESET_FETCH_KEY = 'query-rules-ruleset-fetch';
export const QUERY_RULES_QUERY_RULE_FETCH_KEY = 'query-rules-query-rule-fetch';
export const QUERY_RULES_QUERY_RULESET_EXISTS_KEY = 'query-rules-query-ruleset-exists';

export const CREATE_QUERY_RULE_SET_API_SNIPPET = dedent`# Create or update a query ruleset
PUT /_query_rules/my-ruleset
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React from 'react';

import { EmptyPrompt } from './empty_prompt';
import { render, screen } from '@testing-library/react';
import { act, fireEvent, render, screen } from '@testing-library/react';
import { I18nProvider } from '@kbn/i18n-react';

jest.mock('../../../common/doc_links', () => ({
Expand All @@ -21,16 +21,32 @@ const Wrapper = ({ children }: { children?: React.ReactNode }) => (
);
const mockGetStartedAction = jest.fn();

const TEST_IDS = {
getStartedButton: 'searchQueryRulesEmptyPromptGetStartedButton',
footerLink: 'searchQueryRulesEmptyPromptFooterLink',
};

const ACTIONS = {
getStarted: () => {
act(() => {
fireEvent.click(screen.getByTestId(TEST_IDS.getStartedButton));
});
},
};

describe('Query Rules Overview Empty Prompt', () => {
it('renders', () => {
render(
<Wrapper>
<EmptyPrompt getStartedAction={mockGetStartedAction} />
</Wrapper>
);
expect(screen.getByTestId('searchQueryRulesEmptyPromptGetStartedButton')).toBeInTheDocument();
expect(screen.getByTestId('searchQueryRulesEmptyPromptFooterLink').getAttribute('href')).toBe(
'documentation-url'
);
it('renders correctly', () => {
render(<EmptyPrompt getStartedAction={mockGetStartedAction} />, {
wrapper: Wrapper,
});

expect(screen.getByTestId(TEST_IDS.getStartedButton)).toBeInTheDocument();
expect(screen.getByTestId(TEST_IDS.footerLink).getAttribute('href')).toBe('documentation-url');
});

it('calls getStartedAction when button is clicked', () => {
render(<EmptyPrompt getStartedAction={mockGetStartedAction} />, { wrapper: Wrapper });
ACTIONS.getStarted();
expect(mockGetStartedAction).toHaveBeenCalled();
});
});
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 from 'react';

import {
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiSpacer,
EuiText,
transparentize,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { FormattedMessage } from '@kbn/i18n-react';
import queryRulesImg from '../../assets/query-rules-context-alt.svg';

export const RulesetDetailEmptyPrompt = () => {
const { euiTheme } = useEuiTheme();
const positionRelative = css({
position: 'relative',
});
const imgProps = css({
width: '100%',
height: '100%',
objectFit: 'cover',
});
const gradientOverlay = css({
background: `linear-gradient(180deg, ${transparentize(
euiTheme.colors.backgroundBasePlain,
0
)}, ${transparentize(euiTheme.colors.backgroundBasePlain, 1)} 100%)`,
position: 'absolute',
bottom: 0,
height: '30px',
width: '100%',
});

return (
<EuiPanel hasShadow={false} hasBorder={false} paddingSize="l">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use a panel here vs EuiEmptyPrompt

<EuiSpacer size="l" />
<EuiFlexGroup alignItems="center" justifyContent="center" direction="column">
<EuiFlexItem grow={false}>
<EuiText textAlign="center" size="s" color="subdued">
<FormattedMessage
id="xpack.searchQueryRules.rulesetDetailEmptyPrompt.title"
defaultMessage="Add your first rule"
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow css={positionRelative}>
<img src={queryRulesImg} alt="Query Rules" css={imgProps} />
<div css={gradientOverlay}>&nbsp;</div>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 from 'react';
import { ErrorPrompt } from './error_prompt';
import { render } from '@testing-library/react';
import { I18nProvider } from '@kbn/i18n-react';

const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<I18nProvider>{children}</I18nProvider>
);
describe('ErrorPrompt', () => {
it("renders 'notFound' error type", () => {
const { getByText } = render(<ErrorPrompt errorType="notFound" />, { wrapper: Wrapper });

expect(getByText('Not found')).toBeInTheDocument();
expect(
getByText('Requested resource was not found. Check if the URL is correct.')
).toBeInTheDocument();
});

it("renders 'generic' error type", () => {
const { getByText } = render(<ErrorPrompt errorType="generic" />, { wrapper: Wrapper });

expect(getByText('An error occurred')).toBeInTheDocument();
expect(
getByText(
'An error occurred while fetching query rules. Check Kibana logs for more information.'
)
).toBeInTheDocument();
});

it("renders 'missingPermissions' error type", () => {
const { getByText } = render(<ErrorPrompt errorType="missingPermissions" />, {
wrapper: Wrapper,
});

expect(getByText('Missing permissions')).toBeInTheDocument();
expect(
getByText(
'You do not have the necessary permissions to manage query rules. Contact your system administrator.'
)
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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 { I18nProvider } from '@kbn/i18n-react';
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';
import { CreateRulesetModal } from './create_ruleset_modal';
import { useFetchQueryRulesetExist } from '../../hooks/use_fetch_ruleset_exists';
import { useKibana } from '../../hooks/use_kibana';

const Wrapper = ({ children }: { children: React.ReactNode }) => {
return <I18nProvider>{children}</I18nProvider>;
};

const TEST_IDS = {
ModalHeaderTitle: 'searchRulesetCreateRulesetModalHeader',
NameInput: 'searchRulesetCreateRulesetModalFieldText',
CreateButton: 'searchRulesetCreateRulesetModalCreateButton',
CloseButton: 'searchRulesetCreateRulesetModalCancelButton',
EditLink: 'searchRulesetCreateRulesetModalEditLink',
};

const ACTIONS = {
PressCloseButton: () => {
act(() => {
fireEvent.click(screen.getByTestId(TEST_IDS.CloseButton));
});
},
TypeName: (name: string) => {
const nameInput = screen.getByTestId(TEST_IDS.NameInput) as HTMLInputElement;
act(() => {
fireEvent.change(nameInput, { target: { value: name } });
});
},
PressEditLink: () => {
act(() => {
fireEvent.click(screen.getByTestId(TEST_IDS.EditLink));
});
},
};

const mockOnClose = jest.fn();

const mockUseFetchQueryRulesetExist = useFetchQueryRulesetExist as jest.Mock;
jest.mock('../../hooks/use_fetch_ruleset_exists', () => ({
useFetchQueryRulesetExist: jest.fn().mockImplementation(() => ({
data: undefined,
isLoading: false,
isError: false,
})),
}));

jest.mock('../../hooks/use_kibana', () => ({
useKibana: () => ({
services: {
http: {
basePath: {
prepend: (path: string) => path,
},
},
application: {
navigateToUrl: jest.fn(),
},
},
}),
}));

describe('CreateRulesetModal', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders', () => {
render(<CreateRulesetModal onClose={mockOnClose} />, { wrapper: Wrapper });

expect(screen.getByTestId(TEST_IDS.ModalHeaderTitle).textContent).toBe('Create ruleset');

expect(screen.getByTestId(TEST_IDS.NameInput)).toBeInTheDocument();

expect(screen.getByTestId(TEST_IDS.CreateButton).textContent).toBe('Create ruleset');
});

it('calls onClose when modal is closed', () => {
render(<CreateRulesetModal onClose={mockOnClose} />, { wrapper: Wrapper });

ACTIONS.PressCloseButton();

expect(mockOnClose).toHaveBeenCalled();
});

it('should show conflict error when ruleset name already exists', () => {
mockUseFetchQueryRulesetExist.mockReturnValue({
data: { exists: true },
isLoading: false,
isError: false,
});
render(<CreateRulesetModal onClose={mockOnClose} />, { wrapper: Wrapper });

ACTIONS.TypeName('existing-ruleset');
act(() => {
mockUseFetchQueryRulesetExist.mock.calls[0][2]('existing-ruleset');
});

waitFor(() => {
expect(screen.getByText('Ruleset name already exists')).toBeInTheDocument();
});
waitFor(() => {
expect(screen.getByTestId(TEST_IDS.CreateButton)).toBeDisabled();
});
waitFor(() => {
expect(screen.getByTestId(TEST_IDS.EditLink)).toHaveAttribute(
'href',
'/app/search_query_rules/ruleset/existing-ruleset/edit'
);
});

ACTIONS.PressEditLink();
waitFor(() => {
expect(useKibana().services.application.navigateToUrl).toHaveBeenCalledWith(
'/app/search_query_rules/ruleset/existing-ruleset/edit'
);
});
});

it('should redirect user to create endpoint with given name', () => {
const mockNavigateToUrl = jest.fn();
useKibana().services.application.navigateToUrl = mockNavigateToUrl;

render(<CreateRulesetModal onClose={mockOnClose} />, { wrapper: Wrapper });

ACTIONS.TypeName('new-ruleset');
act(() => {
mockUseFetchQueryRulesetExist.mock.calls[0][1]('new-ruleset');
});

waitFor(() => {
expect(mockNavigateToUrl).toHaveBeenCalledWith(
'/app/search_query_rules/ruleset/new-ruleset/create'
);
});
});
});
Loading