Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RAM] Add shareable rule status filter #130705

Merged
merged 14 commits into from
May 3, 2022
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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, { useState } from 'react';
import { RuleStateFilterProps } from '../../../types';
import { getRuleStateFilterLazy } from '../../../common/get_rule_state_filter';

export const RuleStateFilterSandbox = () => {
const [selectedStates, setSelectedStates] = useState<RuleStateFilterProps['selectedStates']>([]);

return (
<div style={{ flex: 1 }}>
{getRuleStateFilterLazy({
selectedStates,
onChange: setSelectedStates,
})}
<div>Selected states: {JSON.stringify(selectedStates)}</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

import React from 'react';
import { RuleStatusDropdownSandbox } from './rule_status_dropdown_sandbox';
import { RuleStateFilterSandbox } from './rule_state_filter_sandbox';

export const InternalShareableComponentsSandbox: React.FC<{}> = () => {
return (
<>
<RuleStatusDropdownSandbox />
<RuleStateFilterSandbox />
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ export const ActionForm = suspendedComponentWithProps(
export const RuleStatusDropdown = suspendedComponentWithProps(
lazy(() => import('./rules_list/components/rule_status_dropdown'))
);
export const RuleStateFilter = suspendedComponentWithProps(
lazy(() => import('./rules_list/components/rule_state_filter'))
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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 { mountWithIntl } from '@kbn/test-jest-helpers';
import { EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui';
import { RuleStateFilter } from './rule_state_filter';

const onChangeMock = jest.fn();

describe('rule_state_filter', () => {
beforeEach(() => {
onChangeMock.mockReset();
});

it('renders correctly', () => {
const wrapper = mountWithIntl(<RuleStateFilter selectedStates={[]} onChange={onChangeMock} />);

expect(wrapper.find(EuiFilterSelectItem).exists()).toBeFalsy();
expect(wrapper.find(EuiFilterButton).exists()).toBeTruthy();

expect(wrapper.find('.euiNotificationBadge').text()).toEqual('0');
});

it('can open the popover correctly', () => {
const wrapper = mountWithIntl(<RuleStateFilter selectedStates={[]} onChange={onChangeMock} />);

expect(wrapper.find('[data-test-subj="ruleStateFilterSelect"]').exists()).toBeFalsy();

wrapper.find(EuiFilterButton).simulate('click');

const statusItems = wrapper.find(EuiFilterSelectItem);
expect(statusItems.length).toEqual(3);
});

it('can select states', () => {
const wrapper = mountWithIntl(<RuleStateFilter selectedStates={[]} onChange={onChangeMock} />);

wrapper.find(EuiFilterButton).simulate('click');

wrapper.find(EuiFilterSelectItem).at(0).simulate('click');
expect(onChangeMock).toHaveBeenCalledWith(['enabled']);

wrapper.setProps({
selectedStates: ['enabled'],
});

wrapper.find(EuiFilterSelectItem).at(0).simulate('click');
expect(onChangeMock).toHaveBeenCalledWith([]);

wrapper.find(EuiFilterSelectItem).at(1).simulate('click');
expect(onChangeMock).toHaveBeenCalledWith(['enabled', 'disabled']);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* 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, { useState, useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFilterButton, EuiPopover, EuiFilterGroup, EuiFilterSelectItem } from '@elastic/eui';

type State = 'enabled' | 'muted' | 'disabled';
Copy link
Contributor

Choose a reason for hiding this comment

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

@JiaweiWu I know this PR is still a draft, but I would like to give an input at this stage. It should be snoozed instead of muted


const states: State[] = ['enabled', 'disabled', 'muted'];

const optionStyles = {
textTransform: 'capitalize' as const,
};

const getOptionDataTestSubj = (state: State) => `ruleStateFilterOption-${state}`;

export interface RuleStateFilterProps {
selectedStates: State[];
dataTestSubj?: string;
buttonDataTestSubj?: string;
optionDataTestSubj?: (state: State) => string;
onChange: (selectedStates: State[]) => void;
}

export const RuleStateFilter = (props: RuleStateFilterProps) => {
const {
selectedStates = [],
dataTestSubj = 'ruleStateFilterSelect',
buttonDataTestSubj = 'ruleStateFilterButton',
optionDataTestSubj = getOptionDataTestSubj,
onChange = () => {},
} = props;

const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);

const onFilterItemClick = useCallback(
(newOption: State) => () => {
if (selectedStates.includes(newOption)) {
onChange(selectedStates.filter((option) => option !== newOption));
return;
}
onChange([...selectedStates, newOption]);
},
[selectedStates, onChange]
);

const onClick = useCallback(() => {
setIsPopoverOpen((prevIsOpen) => !prevIsOpen);
}, [setIsPopoverOpen]);

return (
<EuiFilterGroup>
<EuiPopover
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
button={
<EuiFilterButton
data-test-subj={buttonDataTestSubj}
iconType="arrowDown"
hasActiveFilters={selectedStates.length > 0}
numActiveFilters={selectedStates.length}
numFilters={selectedStates.length}
onClick={onClick}
>
<FormattedMessage
id="xpack.triggersActionsUI.sections.ruleDetails.ruleStateFilterButton"
defaultMessage="State"
/>
</EuiFilterButton>
}
>
<div data-test-subj={dataTestSubj}>
{states.map((state) => {
return (
<EuiFilterSelectItem
key={state}
style={optionStyles}
data-test-subj={optionDataTestSubj(state)}
onClick={onFilterItemClick(state)}
checked={selectedStates.includes(state) ? 'on' : undefined}
>
{state}
</EuiFilterSelectItem>
);
})}
</div>
</EuiPopover>
</EuiFilterGroup>
);
};

// eslint-disable-next-line import/no-default-export
export { RuleStateFilter as default };
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { RuleStateFilter } from '../application/sections';
import type { RuleStateFilterProps } from '../application/sections/rules_list/components/rule_state_filter';

export const getRuleStateFilterLazy = (props: RuleStateFilterProps) => {
return <RuleStateFilter {...props} />;
};
4 changes: 4 additions & 0 deletions x-pack/plugins/triggers_actions_ui/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from './types';
import { getAlertsTableLazy } from './common/get_alerts_table';
import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown';
import { getRuleStateFilterLazy } from './common/get_rule_state_filter';

function createStartMock(): TriggersAndActionsUIPublicPluginStart {
const actionTypeRegistry = new TypeRegistry<ActionTypeModel>();
Expand Down Expand Up @@ -63,6 +64,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart {
getRuleStatusDropdown: (props) => {
return getRuleStatusDropdownLazy(props);
},
getRuleStateFilter: (props) => {
return getRuleStateFilterLazy(props);
},
};
}

Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/triggers_actions_ui/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { getAddAlertFlyoutLazy } from './common/get_add_alert_flyout';
import { getEditAlertFlyoutLazy } from './common/get_edit_alert_flyout';
import { getAlertsTableLazy } from './common/get_alerts_table';
import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown';
import { getRuleStateFilterLazy } from './common/get_rule_state_filter';
import { ExperimentalFeaturesService } from './common/experimental_features_service';
import {
ExperimentalFeatures,
Expand All @@ -45,6 +46,7 @@ import type {
ConnectorEditFlyoutProps,
AlertsTableProps,
RuleStatusDropdownProps,
RuleStateFilterProps,
AlertsTableConfigurationRegistry,
} from './types';
import { TriggersActionsUiConfigType } from '../common/types';
Expand Down Expand Up @@ -75,6 +77,7 @@ export interface TriggersAndActionsUIPublicPluginStart {
) => ReactElement<RuleEditProps>;
getAlertsTable: (props: AlertsTableProps) => ReactElement<AlertsTableProps>;
getRuleStatusDropdown: (props: RuleStatusDropdownProps) => ReactElement<RuleStatusDropdownProps>;
getRuleStateFilter: (props: RuleStateFilterProps) => ReactElement<RuleStateFilterProps>;
Copy link
Contributor

Choose a reason for hiding this comment

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

@JiaweiWu Do you think we should call it getRuleStatusFilter instead?

}

interface PluginsSetup {
Expand Down Expand Up @@ -244,6 +247,9 @@ export class Plugin
getRuleStatusDropdown: (props: RuleStatusDropdownProps) => {
return getRuleStatusDropdownLazy(props);
},
getRuleStateFilter: (props: RuleStateFilterProps) => {
return getRuleStateFilterLazy(props);
},
};
}

Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/triggers_actions_ui/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
import { RuleRegistrySearchRequestPagination } from '@kbn/rule-registry-plugin/common';
import { TypeRegistry } from './application/type_registry';
import type { ComponentOpts as RuleStatusDropdownProps } from './application/sections/rules_list/components/rule_status_dropdown';

import type { RuleStateFilterProps } from './application/sections/rules_list/components/rule_state_filter';
// In Triggers and Actions we treat all `Alert`s as `SanitizedRule<RuleTypeParams>`
// so the `Params` is a black-box of Record<string, unknown>
type SanitizedRule<Params extends RuleTypeParams = never> = Omit<
Expand Down Expand Up @@ -80,6 +80,7 @@ export type {
ResolvedRule,
SanitizedRule,
RuleStatusDropdownProps,
RuleStateFilterProps,
};
export type { ActionType, AsApiContract };
export {
Expand Down