Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2eb386f
init commit
benakansara Nov 21, 2025
035d5c8
show edit tags flyout
benakansara Nov 21, 2025
40057f8
add tests
benakansara Nov 21, 2025
f9a3c60
cleanup
benakansara Nov 21, 2025
f813798
Merge branch 'main' into feat/edit-workflow-tags
benakansara Nov 21, 2025
41a3964
do not show edit tags for sec rules
benakansara Nov 21, 2025
bff28f2
fix ci errors
benakansara Nov 21, 2025
caf9e6e
fix tests
benakansara Nov 21, 2025
eb81276
Merge branch 'main' into feat/edit-workflow-tags
benakansara Nov 21, 2025
142476d
refactoring
benakansara Nov 21, 2025
18d37d1
refactoring
benakansara Nov 21, 2025
808da51
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Nov 24, 2025
b81e7ee
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Nov 24, 2025
0f089cd
integrate with api
baileycash-elastic Nov 25, 2025
af1c1ce
hook cleanup, fix tests
baileycash-elastic Nov 25, 2025
cc7fd94
fix typing compile issue
baileycash-elastic Nov 25, 2025
8329507
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Nov 25, 2025
300736e
Update src/platform/packages/shared/response-ops/alerts-table/compone…
baileycash-elastic Dec 1, 2025
e2438f8
Update src/platform/packages/shared/response-ops/alerts-table/compone…
baileycash-elastic Dec 1, 2025
37cefd8
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Dec 1, 2025
cbf4ef2
add tests
baileycash-elastic Dec 1, 2025
1e0f845
add context to handle flyout state
baileycash-elastic Dec 2, 2025
92cef02
add more tests for alert grid
baileycash-elastic Dec 2, 2025
566b31c
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Dec 2, 2025
aac5b24
general cleanup
baileycash-elastic Dec 2, 2025
75b4c78
Merge remote-tracking branch 'refs/remotes/benakansara/feat/edit-work…
baileycash-elastic Dec 2, 2025
69e82d3
consolidate hook functionality
baileycash-elastic Dec 2, 2025
cc5b7bf
use generic index to update alerts
baileycash-elastic Dec 2, 2025
f7b6233
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Dec 2, 2025
6f522bf
fix nits, add api assertions to tag action tests
baileycash-elastic Dec 3, 2025
d7badb2
add capabilities check to tags
baileycash-elastic Dec 3, 2025
8569255
add tests for edit action
baileycash-elastic Dec 3, 2025
20a8847
attempt to fix flaky test
baileycash-elastic Dec 3, 2025
b13a985
other flaky test fix
baileycash-elastic Dec 3, 2025
3d0b25b
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Dec 3, 2025
4589b85
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Dec 4, 2025
ec64aa8
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Dec 4, 2025
a47954b
Merge branch 'main' into feat/edit-workflow-tags
baileycash-elastic Dec 4, 2025
f609083
update header
benakansara Dec 5, 2025
e369613
Merge branch 'main' into feat/edit-workflow-tags
benakansara Dec 5, 2025
57e4e10
simplify tests, address feedback
baileycash-elastic Dec 5, 2025
b49a6cc
fix close flyout on error
benakansara Dec 5, 2025
5b1d30e
Merge branch 'main' into feat/edit-workflow-tags
benakansara Dec 5, 2025
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 @@ -17,6 +17,10 @@ import type { AlertsDataGridProps, BulkActionsState } from '../types';
import type { AdditionalContext, RenderContext } from '../types';
import type { EuiDataGridColumnCellAction } from '@elastic/eui';
import { EuiButton, EuiButtonIcon, EuiFlexItem } from '@elastic/eui';
import { QueryClient, QueryClientProvider } from '@kbn/react-query';
import { testQueryClientConfig } from '@kbn/alerts-ui-shared/src/common/test_utils/test_query_client_config';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context';
import { bulkActionsReducer } from '../reducers/bulk_actions_reducer';
import { getJsDomPerformanceFix } from '../utils/test';
import { useCaseViewNavigation } from '../hooks/use_case_view_navigation';
Expand All @@ -35,12 +39,11 @@ import {
FIELD_BROWSER_CUSTOM_CREATE_BTN_TEST_ID,
FIELD_BROWSER_TEST_ID,
} from '../constants';
import { QueryClient, QueryClientProvider } from '@kbn/react-query';
import { testQueryClientConfig } from '@kbn/alerts-ui-shared/src/common/test_utils/test_query_client_config';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context';
import { useIndividualTagsActionContext } from '../contexts/individual_tags_action_context';
import { useTagsAction } from './tags/use_tags_action';

jest.mock('../hooks/use_case_view_navigation');
jest.mock('./tags/use_tags_action');

const cellActionOnClickMockedFn = jest.fn();
const mockOnChangeVisibleColumns = jest.fn();
Expand Down Expand Up @@ -133,6 +136,17 @@ describe('AlertsDataGrid', () => {

beforeEach(() => {
jest.clearAllMocks();
// Reset the tags action mock to default state
const mockUseTagsAction = jest.mocked(useTagsAction);
mockUseTagsAction.mockReset();
mockUseTagsAction.mockImplementation(() => ({
isFlyoutOpen: false,
selectedAlerts: [],
openFlyout: jest.fn(),
onClose: jest.fn(),
onSaveTags: jest.fn(),
getAction: jest.fn(),
}));
});

describe('Alerts table UI', () => {
Expand Down Expand Up @@ -585,5 +599,125 @@ describe('AlertsDataGrid', () => {
expect(container.querySelector('.euiDataGrid__virtualized')).toBeTruthy();
});
});

describe('Individual tags flyout', () => {
const mockUseTagsAction = jest.mocked(useTagsAction);
const mockAlert = {
_id: 'alert-1',
_index: 'test-index',
version: 'v1',
title: 'Test Alert',
'kibana.alert.workflow_tags': ['tag1', 'tag2'],
} as any;

beforeEach(() => {
// Reset the mock before each test
mockUseTagsAction.mockReset();
// Default mock: flyout closed
mockUseTagsAction.mockImplementation(() => ({
isFlyoutOpen: false,
selectedAlerts: [],
openFlyout: jest.fn(),
onClose: jest.fn(),
onSaveTags: jest.fn(),
getAction: jest.fn(),
}));
});

it('should not render individual tags flyout when closed', async () => {
render(<TestComponent {...mockDataGridProps} />);

// By default, the flyout should be closed
expect(screen.queryByTestId('alerts-edit-tags-flyout')).not.toBeInTheDocument();
});

it('should render individual tags flyout when opened', async () => {
const mockOnClose = jest.fn();
const mockOnSaveTags = jest.fn();

// Set up the mock implementation before rendering
mockUseTagsAction.mockImplementation(() => ({
isFlyoutOpen: true,
selectedAlerts: [mockAlert],
openFlyout: jest.fn(),
onClose: mockOnClose,
onSaveTags: mockOnSaveTags,
getAction: jest.fn(),
}));

render(<TestComponent {...mockDataGridProps} />);

expect(await screen.findByTestId('alerts-edit-tags-flyout')).toBeInTheDocument();
expect(screen.getByTestId('alerts-edit-tags-flyout-title')).toBeInTheDocument();
});

it('should call onClose when cancel button is clicked', async () => {
const mockOnClose = jest.fn();
const mockOnSaveTags = jest.fn();

mockUseTagsAction.mockImplementation(() => ({
isFlyoutOpen: true,
selectedAlerts: [mockAlert],
openFlyout: jest.fn(),
onClose: mockOnClose,
onSaveTags: mockOnSaveTags,
getAction: jest.fn(),
}));

render(<TestComponent {...mockDataGridProps} />);

const cancelButton = await screen.findByTestId('alerts-edit-tags-flyout-cancel');
await userEvent.click(cancelButton);

// The onClose should be called
expect(mockOnClose).toHaveBeenCalled();
});

it('should provide IndividualTagsActionContext to child components', async () => {
const TestChildComponent = () => {
try {
const context = useIndividualTagsActionContext();
return (
<div data-test-subj="context-test">
{context.isFlyoutOpen ? 'Flyout Open' : 'Flyout Closed'}
</div>
);
} catch (e) {
return <div data-test-subj="context-test">Context Not Available</div>;
}
};

render(
<TestComponent
{...mockDataGridProps}
renderContext={{
renderActionsCell: () => <TestChildComponent />,
}}
/>
);

// The context should be available (flyout closed by default)
expect(await screen.findByTestId('context-test')).toHaveTextContent('Flyout Closed');
});

it('should render individual tags flyout with correct alert data', async () => {
mockUseTagsAction.mockImplementation(() => ({
isFlyoutOpen: true,
selectedAlerts: [mockAlert],
openFlyout: jest.fn(),
onClose: jest.fn(),
onSaveTags: jest.fn(),
getAction: jest.fn(),
}));

render(<TestComponent {...mockDataGridProps} />);

const flyout = await screen.findByTestId('alerts-edit-tags-flyout');
expect(flyout).toBeInTheDocument();

// Verify the flyout title is present
expect(screen.getByTestId('alerts-edit-tags-flyout-title')).toBeInTheDocument();
});
});
});
});
Comment thread
baileycash-elastic marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import { useSorting } from '../hooks/use_sorting';
import { CellPopoverHost } from './cell_popover_host';
import { NonVirtualizedGridBody } from './non_virtualized_grid_body';
import type { AlertDetailFlyout as AlertDetailFlyoutType } from './alert_detail_flyout';
import { EditTagsFlyout } from './tags/edit_tags_flyout';
import { IndividualTagsActionContextProvider } from '../contexts/individual_tags_action_context';
import { useTagsAction } from './tags/use_tags_action';

const AlertDetailFlyout = lazy(
() => import('./alert_detail_flyout')
Expand Down Expand Up @@ -111,6 +114,7 @@ export const AlertsDataGrid = typedMemo(
bulkActions,
setIsBulkActionsLoading,
clearSelection,
bulkEditTagsFlyoutState,
} = useBulkActions({
ruleTypeIds,
query,
Expand Down Expand Up @@ -326,46 +330,82 @@ export const AlertsDataGrid = typedMemo(
: // Overriding the simplified type here to avoid cyclic problems with generics
(AlertDetailFlyout as NonNullable<typeof renderExpandedAlertView>);

const selectedAlerts = useMemo(
() =>
Array.from(bulkActionsState.rowSelection.keys())
.map((i) => alerts[i])
Comment thread
baileycash-elastic marked this conversation as resolved.
.filter(Boolean),
[alerts, bulkActionsState.rowSelection]
);

const individualTagsFlyout = useTagsAction({
onActionSuccess: () => {
refresh();
},
onActionError: () => {
refresh();
},
isDisabled: false,
});

const { selectedAlerts: selectedAlertsFromRowAction } = individualTagsFlyout;

return (
<InspectButtonContainer>
<section style={{ width: '100%' }} data-test-subj={props['data-test-subj']}>
<Suspense fallback={null}>
{expandedAlertIndex != null && ExpandedAlertView && (
<ExpandedAlertView {...renderContext} expandedAlertIndex={expandedAlertIndex} />
<IndividualTagsActionContextProvider value={individualTagsFlyout}>
<InspectButtonContainer>
<section style={{ width: '100%' }} data-test-subj={props['data-test-subj']}>
<Suspense fallback={null}>
{expandedAlertIndex != null && ExpandedAlertView && (
<ExpandedAlertView {...renderContext} expandedAlertIndex={expandedAlertIndex} />
)}
</Suspense>
{bulkEditTagsFlyoutState.isFlyoutOpen && selectedAlerts.length > 0 && (
<EditTagsFlyout
selectedAlerts={selectedAlerts}
onClose={bulkEditTagsFlyoutState.onClose}
onSaveTags={bulkEditTagsFlyoutState.onSaveTags}
/>
)}
{individualTagsFlyout.isFlyoutOpen && selectedAlertsFromRowAction?.length > 0 && (
<EditTagsFlyout
selectedAlerts={selectedAlertsFromRowAction}
onClose={individualTagsFlyout.onClose}
onSaveTags={individualTagsFlyout.onSaveTags}
/>
)}
{alertsCount > 0 && (
<EuiDataGrid
{...euiDataGridProps}
// As per EUI docs, it is not recommended to switch between undefined and defined height.
// If user changes height, it is better to unmount and mount the component.
// Ref: https://eui.elastic.co/#/tabular-content/data-grid#virtualization
key={height ? 'fixedHeight' : 'autoHeight'}
ref={dataGridRef}
css={rowStyles}
aria-label="Alerts table"
data-test-subj={isLoading ? `alertsTableIsLoading` : `alertsTableIsLoaded`}
height={height}
columns={columnsWithCellActions}
columnVisibility={columnVisibility}
trailingControlColumns={trailingControlColumns}
leadingControlColumns={leadingControlColumns}
rowCount={alertsCount}
renderCustomGridBody={dynamicRowHeight ? renderCustomGridBody : undefined}
cellContext={renderContext}
// Cast necessary because `cellContext` is untyped in EuiDataGrid
renderCellValue={CellValueHost as RenderCellValue}
renderCellPopover={CellPopoverHost}
gridStyle={actualGridStyle}
sorting={sortProps}
toolbarVisibility={toolbarVisibility}
pagination={dataGridPagination}
rowHeightsOptions={rowHeightsOptions}
onColumnResize={onColumnResize}
/>
)}
</Suspense>
{alertsCount > 0 && (
<EuiDataGrid
{...euiDataGridProps}
// As per EUI docs, it is not recommended to switch between undefined and defined height.
// If user changes height, it is better to unmount and mount the component.
// Ref: https://eui.elastic.co/#/tabular-content/data-grid#virtualization
key={height ? 'fixedHeight' : 'autoHeight'}
ref={dataGridRef}
css={rowStyles}
aria-label="Alerts table"
data-test-subj={isLoading ? `alertsTableIsLoading` : `alertsTableIsLoaded`}
height={height}
columns={columnsWithCellActions}
columnVisibility={columnVisibility}
trailingControlColumns={trailingControlColumns}
leadingControlColumns={leadingControlColumns}
rowCount={alertsCount}
renderCustomGridBody={dynamicRowHeight ? renderCustomGridBody : undefined}
cellContext={renderContext}
// Cast necessary because `cellContext` is untyped in EuiDataGrid
renderCellValue={CellValueHost as RenderCellValue}
renderCellPopover={CellPopoverHost}
gridStyle={actualGridStyle}
sorting={sortProps}
toolbarVisibility={toolbarVisibility}
pagination={dataGridPagination}
rowHeightsOptions={rowHeightsOptions}
onColumnResize={onColumnResize}
/>
)}
</section>
</InspectButtonContainer>
</section>
</InspectButtonContainer>
</IndividualTagsActionContextProvider>
);
}
);
Expand Down
Loading