Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ee7493d
Add bulk actions to message widget
maxiadlovskii Mar 13, 2026
957765f
Add bulk actions to message widget
maxiadlovskii Mar 13, 2026
3eea27d
Add Provider for selectable items
maxiadlovskii Mar 13, 2026
99b775b
fix modal and styles
maxiadlovskii Mar 16, 2026
76b05d0
Use index and message id as identifier for selected message
maxiadlovskii Mar 16, 2026
c61826b
Use contexts for table message in log view. Use index and id to ident…
maxiadlovskii Mar 17, 2026
1fc9da1
add licenses
maxiadlovskii Mar 17, 2026
f791daa
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 17, 2026
fb536fc
tsc and test fixes
maxiadlovskii Mar 18, 2026
df2b602
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 18, 2026
4c8867b
revert yarnlock
maxiadlovskii Mar 18, 2026
f74a391
add few more tests
maxiadlovskii Mar 18, 2026
3fbf149
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 18, 2026
29ec158
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 18, 2026
46b040a
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 19, 2026
5d7f3f6
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
linuspahl Mar 20, 2026
a75416a
Improve padding of bulk checkboxes to avoid increases height of messa…
linuspahl Mar 20, 2026
50e2914
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 20, 2026
c3d7368
fix issue on pagination
maxiadlovskii Mar 18, 2026
4309542
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
linuspahl Mar 20, 2026
648d6bc
Fixing linter hint
linuspahl Mar 20, 2026
ac42d54
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 20, 2026
bd7279c
Merge branch 'master' into feat/Add-bulk-actions-to-message-widget
maxiadlovskii Mar 20, 2026
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 @@ -21,11 +21,13 @@ import { Checkbox } from 'components/bootstrap';
const RowCheckbox = styled(Checkbox)`
&.checkbox {
margin: 0;
padding-top: 2px;

label {
display: flex;
align-items: center;
padding: 0;
min-height: 14px;

input {
width: 14px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { BULK_SELECT_COLUMN_WIDTH } from 'components/common/EntityDataTable/Constants';

const StyledCell = styled.td`
&&& {
width: ${BULK_SELECT_COLUMN_WIDTH}px;
min-width: auto;
}
`;

const BulkSelectCell = ({ children = null }: React.PropsWithChildren) => (
<StyledCell onClick={(event) => event.stopPropagation()}>{children}</StyledCell>
);

export default BulkSelectCell;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 RowCheckbox from 'components/common/EntityDataTable/RowCheckbox';
import type { SelectableMessageTableMessage } from 'views/components/widgets/MessageList';
import useSelectedMessageEntities from 'views/hooks/useSelectedMessageEntities';

const BulkSelectHead = ({ data }: { data: Array<SelectableMessageTableMessage> }) => {
const { toggleAllEntitySelect, isAllRowsSelected, isSomeRowsSelected } = useSelectedMessageEntities();
const title = `${isAllRowsSelected ? 'Deselect' : 'Select'} all visible messages`;

const onBulkSelect = () => toggleAllEntitySelect(data);

return (
<RowCheckbox
indeterminate={isSomeRowsSelected}
onChange={onBulkSelect}
checked={isAllRowsSelected}
title={title}
disabled={!data.length}
aria-label={title}
/>
);
};

export default BulkSelectHead;
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ describe('MessagePreview', () => {
showMessageRow
config={MessagesWidgetConfig.builder().build()}
messageFieldType={new FieldType('string', [], [])}
displayBulkSelectCol={false}
{...rest}
/>
</tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type MessagesWidgetConfig from 'views/logic/widgets/MessagesWidgetConfig'
import type FieldType from 'views/logic/fieldtypes/FieldType';
import type { Message } from 'views/components/messagelist/Types';
import usePluginEntities from 'hooks/usePluginEntities';
import BulkSelectCell from 'components/common/message/messagetable/BulkSelectCell';

import MessageFieldRow from './MessageFieldRow';

Expand Down Expand Up @@ -51,6 +52,7 @@ type Props = {
showMessageRow?: boolean;
messageFieldType: FieldType;
config: MessagesWidgetConfig;
displayBulkSelectCol: boolean;
};

const MessagePreview = ({
Expand All @@ -60,12 +62,14 @@ const MessagePreview = ({
messageFieldType,
showMessageRow = false,
config,
displayBulkSelectCol,
}: Props) => {
const MessageRowOverride = usePluginEntities('views.components.widgets.messageTable.messageRowOverride')?.[0];

return (
showMessageRow && (
<TableRow onClick={onRowClick}>
{displayBulkSelectCol && <BulkSelectCell />}
<td colSpan={colSpanFixup}>
{!!MessageRowOverride && (
<MessageRowOverride
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ import Field from 'views/components/Field';
import useAutoRefresh from 'views/hooks/useAutoRefresh';
import { TableHeaderCell, TableHead } from 'views/components/datatable';
import InteractiveContext from 'views/components/contexts/InteractiveContext';
import useSelectableMessageTableMessages from 'views/components/widgets/useSelectableMessageTableMessages';
import BulkSelectCell from 'components/common/message/messagetable/BulkSelectCell';

import FieldSortIcon from './FieldSortIcon';
import MessageTableEntry from './MessageTableEntry';
import MessageTableProviders from './MessageTableProviders';
import BulkSelectHead from './BulkSelectHead';

const Table = styled.table(
({ theme }) => css`
Expand Down Expand Up @@ -95,6 +98,8 @@ type Props = {
renderRowActions?: (message: Message) => React.ReactNode;
scrollContainerRef: React.MutableRefObject<HTMLDivElement>;
setLoadingState: (loading: boolean) => void;
displayBulkSelectCol?: boolean;
isEntitySelectable?: (entity: BackendMessage) => boolean;
};

const _fieldTypeFor = (fieldName: string, fields: Immutable.List<FieldTypeMapping>) =>
Expand Down Expand Up @@ -132,12 +137,15 @@ const MessageTable = ({
fields,
messages,
config,
onSortChange,
onSortChange = undefined,
renderRowActions = undefined,
setLoadingState,
scrollContainerRef,
displayBulkSelectCol = false,
isEntitySelectable = () => false,
}: Props) => {
const { stopAutoRefresh } = useAutoRefresh();
const { selectableMessageTableMessages } = useSelectableMessageTableMessages();
const [expandedMessages, setExpandedMessages] = useState(Immutable.Set<string>());
const formattedMessages = useMemo(() => _getFormattedMessages(messages), [messages]);
const selectedFields = useMemo(() => Immutable.OrderedSet<string>(config?.fields ?? []), [config?.fields]);
Expand All @@ -156,6 +164,11 @@ const MessageTable = ({
<Table className="table table-condensed">
<TableHead>
<tr>
{displayBulkSelectCol && (
<BulkSelectCell>
<BulkSelectHead data={selectableMessageTableMessages} />
</BulkSelectCell>
)}
{selectedFields
.toSeq()
.map((selectedFieldName) => {
Expand Down Expand Up @@ -197,6 +210,8 @@ const MessageTable = ({
expanded={expandedMessages.contains(messageKey)}
toggleDetail={toggleDetail}
expandAllRenderAsync={false}
displayBulkSelectCol={displayBulkSelectCol}
isEntitySelectable={isEntitySelectable}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import * as Immutable from 'immutable';
import MockStore from 'helpers/mocking/StoreMock';
import MockAction from 'helpers/mocking/MockAction';
import MessagesWidgetConfig from 'views/logic/widgets/MessagesWidgetConfig';
import MessageTableSelectedEntitiesProvider from 'views/components/widgets/MessageTableSelectedEntitiesProvider';
import SelectableMessageTableMessagesProvider from 'views/components/widgets/SelectableMessageTableMessagesProvider';

import MessageTableEntry from './MessageTableEntry';

Expand All @@ -42,17 +44,21 @@ describe('MessageTableEntry', () => {
};

render(
<table>
<MessageTableEntry
expandAllRenderAsync
toggleDetail={() => {}}
fields={Immutable.List()}
message={message}
config={MessagesWidgetConfig.builder().build()}
selectedFields={Immutable.OrderedSet(['message', 'notexisting'])}
expanded={false}
/>
</table>,
<SelectableMessageTableMessagesProvider displayBulkSelectCol={false} messages={[]}>
<MessageTableSelectedEntitiesProvider bulkSelection={null}>
<table>
<MessageTableEntry
expandAllRenderAsync
toggleDetail={() => {}}
fields={Immutable.List()}
message={message}
config={MessagesWidgetConfig.builder().build()}
selectedFields={Immutable.OrderedSet(['message', 'notexisting'])}
expanded={false}
/>
</table>
</MessageTableSelectedEntitiesProvider>
</SelectableMessageTableMessagesProvider>,
);

expect(screen.getByText('Something happened!')).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
import { TableDataCell } from 'views/components/datatable';
import MessageDetail from 'components/common/message/details/MessageDetail';
import DecoratedValue from 'views/components/messagelist/decoration/DecoratedValue';
import type { Message } from 'views/components/messagelist/Types';
import type { BackendMessage, Message } from 'views/components/messagelist/Types';
import CustomHighlighting from 'views/components/highlighting/CustomHighlighting';
import TypeSpecificValue from 'views/components/TypeSpecificValue';
import HighlightMessageContext from 'views/components/contexts/HighlightMessageContext';
import { toSelectableMessageTableEntry } from 'views/components/widgets/MessageTableSelectedEntitiesProvider';
import RowCheckbox from 'components/common/EntityDataTable/RowCheckbox';
import BulkSelectCell from 'components/common/message/messagetable/BulkSelectCell';
import useSelectedMessageEntities from 'views/hooks/useSelectedMessageEntities';

import MessagePreview from './MessagePreview';

Expand Down Expand Up @@ -111,6 +115,8 @@ type Props = {
selectedFields?: Immutable.OrderedSet<string>;
showMessageRow?: boolean;
toggleDetail: (messageId: string) => void;
displayBulkSelectCol?: boolean;
isEntitySelectable?: (entity: BackendMessage) => boolean;
};

const isDecoratedField = (field: string | number, decorationStats: Message['decoration_stats']) =>
Expand All @@ -136,10 +142,13 @@ const MessageTableEntry = ({
showMessageRow = false,
selectedFields = Immutable.OrderedSet<string>(),
toggleDetail,
displayBulkSelectCol = false,
isEntitySelectable = () => false,
}: Props) => {
const { inputs: inputsList = [] } = useStore(InputsStore);
const { streams: streamsList = [] } = useStore(StreamsStore);
const highlightMessageId = useContext(HighlightMessageContext);
const { toggleEntitySelect, isEntitySelected } = useSelectedMessageEntities();
const sendTelemetry = useSendTelemetry();
const additionalContextValue = useMemo(() => ({ message }), [message]);
const allStreams = useMemo(() => Immutable.List<Stream>(streamsList), [streamsList]);
Expand All @@ -165,7 +174,10 @@ const MessageTableEntry = ({
}
}, [message.id, message.index, sendTelemetry, toggleDetail]);

const colSpanFixup = selectedFields.size + 1 + (rowActions ? 1 : 0);
const isSelected = isEntitySelected(message.index, message.id);
const checkboxTitle = `${isSelected ? 'Deselect' : 'Select'} message`;
const isSelectDisabled = !displayBulkSelectCol || !isEntitySelectable(toSelectableMessageTableEntry(message));
const colSpanFixup = selectedFields.size + 1 + (rowActions ? 1 : 0) + (displayBulkSelectCol ? 1 : 0);

const selectedFieldsList = useMemo(
() =>
Expand Down Expand Up @@ -200,6 +212,17 @@ const MessageTableEntry = ({
<AdditionalContext.Provider value={additionalContextValue}>
<TableBody $expanded={expanded} $highlighted={message.id === highlightMessageId}>
<FieldsRow onClick={_toggleDetail} className="table-data-row">
{displayBulkSelectCol && (
<BulkSelectCell>
<RowCheckbox
onChange={() => toggleEntitySelect(message.index, message.id)}
title={!isSelectDisabled ? checkboxTitle : undefined}
checked={isSelected}
disabled={isSelectDisabled}
aria-label={checkboxTitle}
/>
</BulkSelectCell>
)}
{selectedFieldsList}
{rowActions && <ActionsCell $isNumeric={false}>{rowActions}</ActionsCell>}
</FieldsRow>
Expand All @@ -211,6 +234,7 @@ const MessageTableEntry = ({
messageFieldType={messageFieldType}
onRowClick={_toggleDetail}
message={message}
displayBulkSelectCol={displayBulkSelectCol}
/>

{expanded && (
Expand Down
Loading
Loading