diff --git a/changelog/unreleased/issue-25517.toml b/changelog/unreleased/issue-25517.toml new file mode 100644 index 000000000000..ac588c8c6405 --- /dev/null +++ b/changelog/unreleased/issue-25517.toml @@ -0,0 +1,5 @@ +type = "f" +message = "Fix issue when default favorite fields disappear after adding favorite field." + +pulls = ["25530"] +issues = ["25517"] diff --git a/graylog2-web-interface/src/components/common/message/details/context/MessageFavoriteFieldsProvider.tsx b/graylog2-web-interface/src/components/common/message/details/context/MessageFavoriteFieldsProvider.tsx index 4af4c9644329..0b55ace1bdef 100644 --- a/graylog2-web-interface/src/components/common/message/details/context/MessageFavoriteFieldsProvider.tsx +++ b/graylog2-web-interface/src/components/common/message/details/context/MessageFavoriteFieldsProvider.tsx @@ -28,9 +28,8 @@ import { StreamsStore } from 'views/stores/StreamsStore'; import type { Stream } from 'logic/streams/types'; import { isPermitted } from 'util/PermissionsMixin'; import useCurrentUser from 'hooks/useCurrentUser'; - -import { DEFAULT_FIELDS } from '../fields/hooks/useMessageFavoriteFieldsForEditing'; -import useMessageFavoriteFieldsMutation from '../fields/hooks/useMessageFavoriteFieldsMutation'; +import { getStreamFavoriteFields } from 'components/common/message/helpers'; +import useMessageFavoriteFieldsMutation from 'components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation'; type OriginalProps = React.PropsWithChildren<{ message: Message; @@ -51,18 +50,28 @@ const OriginalMessageFavoriteFieldsProvider = ({ children = null, message, messa return messageStreamIds.map((id) => streamsById?.[id]).filter((s) => !!s); }, [message?.fields?.streams, permissions, streamsList]); + const initialFavoriteFieldsByStream = useMemo( + () => Object.fromEntries(streams.map((stream) => [stream.id, getStreamFavoriteFields(stream, message?.fields)])), + [message?.fields, streams], + ); + const initialFavoriteFields = useMemo( - () => uniq(flattenDeep(zip(streams.map((stream) => stream?.favorite_fields ?? DEFAULT_FIELDS)))), - [streams], + () => uniq(flattenDeep(zip(Object.values(initialFavoriteFieldsByStream)))), + [initialFavoriteFieldsByStream], ); const editableStreams = useMemo( - () => streams.filter((stream: Stream) => isPermitted(permissions, `streams:edit:${stream.id}`)), + () => streams.filter((stream) => isPermitted(permissions, `streams:edit:${stream.id}`)), [permissions, streams], ); + const editableStreamsInitialFavoriteFields = useMemo( + () => Object.fromEntries(editableStreams.map(({ id }) => [id, initialFavoriteFieldsByStream[id]])), + [editableStreams, initialFavoriteFieldsByStream], + ); + const { saveFavoriteField, toggleField, setFieldsIsPending } = useMessageFavoriteFieldsMutation( - editableStreams, + editableStreamsInitialFavoriteFields, initialFavoriteFields, ); @@ -75,6 +84,7 @@ const OriginalMessageFavoriteFieldsProvider = ({ children = null, message, messa toggleField, editableStreams, setFieldsIsPending, + initialFavoriteFieldsByStream, }), [ initialFavoriteFields, @@ -82,8 +92,9 @@ const OriginalMessageFavoriteFieldsProvider = ({ children = null, message, messa messageFields, message, toggleField, - editableStreams, setFieldsIsPending, + initialFavoriteFieldsByStream, + editableStreams, ], ); diff --git a/graylog2-web-interface/src/components/common/message/details/fields/MessageFieldsEditModal.tsx b/graylog2-web-interface/src/components/common/message/details/fields/MessageFieldsEditModal.tsx index 8bf5aa57f955..58638b24047d 100644 --- a/graylog2-web-interface/src/components/common/message/details/fields/MessageFieldsEditModal.tsx +++ b/graylog2-web-interface/src/components/common/message/details/fields/MessageFieldsEditModal.tsx @@ -25,8 +25,9 @@ import { StreamsStore } from 'views/stores/StreamsStore'; import MessageFavoriteFieldsContext from 'views/components/contexts/MessageFavoriteFieldsContext'; import StringUtils from 'util/StringUtils'; import { ModalSubmit } from 'components/common'; +import { DEFAULT_FIELDS } from 'components/common/message/helpers'; -import useMessageFavoriteFieldsForEditing, { DEFAULT_FIELDS } from './hooks/useMessageFavoriteFieldsForEditing'; +import useMessageFavoriteFieldsForEditing from './hooks/useMessageFavoriteFieldsForEditing'; import MessageFieldsEditModeLists from './MessageFieldsEditModeLists'; import useSendFavoriteFieldTelemetry from './hooks/useSendFavoriteFieldTelemetry'; diff --git a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.test.tsx b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.test.tsx index b99291095f82..95a820b5f422 100644 --- a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.test.tsx +++ b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.test.tsx @@ -47,6 +47,9 @@ const createWrapper = message: undefined, editableStreams: [], setFieldsIsPending: false, + initialFavoriteFieldsByStream: { + streamId: initialFavorites, + }, }}> {children} diff --git a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.ts b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.ts index 3062e7209bc7..a9ff9f18f5ff 100644 --- a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.ts +++ b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsForEditing.ts @@ -18,13 +18,11 @@ import { useState, useCallback, useContext } from 'react'; import uniq from 'lodash/uniq'; import MessageFavoriteFieldsContext from 'views/components/contexts/MessageFavoriteFieldsContext'; +import type { FormattedField } from 'components/common/message/details/fields/types'; +import { DEFAULT_FIELDS } from 'components/common/message/helpers'; import useSendFavoriteFieldTelemetry from './useSendFavoriteFieldTelemetry'; -import type { FormattedField } from '../types'; - -export const DEFAULT_FIELDS = ['source', 'destination_ip', 'usernames']; - const useMessageFavoriteFieldsForEditing = () => { const sendFavoriteFieldTelemetry = useSendFavoriteFieldTelemetry(); const { saveFavoriteField, favoriteFields: initialFavoriteFields } = useContext(MessageFavoriteFieldsContext); diff --git a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.test.tsx b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.test.tsx index 34b39e5f2a32..53ffe992dc5f 100644 --- a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.test.tsx +++ b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.test.tsx @@ -62,7 +62,14 @@ const queryClient = new QueryClient({ const wrapper = ({ children }) => {children}; const renderTestHook = (streams: Array, initialFavoriteFields: Array) => - renderHook(() => useMessageFavoriteFieldsMutation(streams, initialFavoriteFields), { wrapper }); + renderHook( + () => + useMessageFavoriteFieldsMutation( + Object.fromEntries(streams.map((stream) => [stream.id, stream.favorite_fields])), + initialFavoriteFields, + ), + { wrapper }, + ); describe('useMessageFavoriteFieldsMutation', () => { const streams: Array = [ { diff --git a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.ts b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.ts index d7e87c91e97e..d9fd1fa50f8c 100644 --- a/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.ts +++ b/graylog2-web-interface/src/components/common/message/details/fields/hooks/useMessageFavoriteFieldsMutation.ts @@ -16,14 +16,13 @@ */ import { useCallback } from 'react'; import { useMutation } from '@tanstack/react-query'; +import mapValues from 'lodash/mapValues'; import { FavoriteFields } from '@graylog/server-api'; import { StreamsActions } from 'views/stores/StreamsStore'; import UserNotification from 'util/UserNotification'; -import type { Stream } from 'logic/streams/types'; - -import useSendFavoriteFieldTelemetry from './useSendFavoriteFieldTelemetry'; +import useSendFavoriteFieldTelemetry from 'components/common/message/details/fields/hooks/useSendFavoriteFieldTelemetry'; interface FavoriteFieldRequest { readonly field: string; @@ -36,7 +35,10 @@ interface SetFavoriteFieldsRequest { }; } -const useMessageFavoriteFieldsMutation = (streams: Array, initialFavoriteFields: Array) => { +const useMessageFavoriteFieldsMutation = ( + initialFavoriteFieldsByStream: Record>, + initialFavoriteFields: Array, +) => { const sendFavoriteFieldTelemetry = useSendFavoriteFieldTelemetry(); const { isPending: setFieldsIsPending, mutate: setFavoriteFields } = useMutation({ mutationFn: (props: SetFavoriteFieldsRequest) => FavoriteFields.set(props), @@ -90,26 +92,19 @@ const useMessageFavoriteFieldsMutation = (streams: Array, initialFavorit (favoritesToSave: Array) => { const newAddedFields = favoritesToSave.filter((f) => !initialFavoriteFields.includes(f)); - const newFavoriteFieldsByStream = Object.fromEntries( - streams.map((stream) => [ - stream.id, - favoritesToSave.filter((f) => { - const streamFavoriteFields = stream?.favorite_fields ?? []; - - return streamFavoriteFields.includes(f) || newAddedFields.includes(f); - }), - ]), + const newFavoriteFieldsByStream = mapValues(initialFavoriteFieldsByStream, (streamFavoriteFields) => + favoritesToSave.filter((f) => streamFavoriteFields.includes(f) || newAddedFields.includes(f)), ); setFavoriteFields({ fields: newFavoriteFieldsByStream }); }, - [initialFavoriteFields, setFavoriteFields, streams], + [initialFavoriteFields, initialFavoriteFieldsByStream, setFavoriteFields], ); const toggleField = useCallback( (field: string) => { const isFavorite = initialFavoriteFields?.includes(field); - const streamIds = streams.map((stream) => stream.id); + const streamIds = Object.keys(initialFavoriteFieldsByStream); if (isFavorite) { removeFavoriteField({ field, stream_ids: streamIds }); @@ -117,7 +112,7 @@ const useMessageFavoriteFieldsMutation = (streams: Array, initialFavorit addFavoriteField({ field, stream_ids: streamIds }); } }, - [addFavoriteField, initialFavoriteFields, removeFavoriteField, streams], + [addFavoriteField, initialFavoriteFields, initialFavoriteFieldsByStream, removeFavoriteField], ); return { diff --git a/graylog2-web-interface/src/components/common/message/helpers.ts b/graylog2-web-interface/src/components/common/message/helpers.ts new file mode 100644 index 000000000000..faa1ea83cbb1 --- /dev/null +++ b/graylog2-web-interface/src/components/common/message/helpers.ts @@ -0,0 +1,23 @@ +/* + * 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 + * . + */ +import type { Stream } from 'logic/streams/types'; +import type { Message } from 'views/components/messagelist/Types'; + +export const DEFAULT_FIELDS = ['source', 'destination_ip', 'usernames']; + +export const getStreamFavoriteFields = (stream: Stream, fields: Message['fields'] = {}) => + stream?.favorite_fields ?? DEFAULT_FIELDS.filter((field) => Object.hasOwn(fields, field)); diff --git a/graylog2-web-interface/src/views/components/contexts/MessageFavoriteFieldsContext.ts b/graylog2-web-interface/src/views/components/contexts/MessageFavoriteFieldsContext.ts index 7a0db2a7c7ae..5afeed0e8c99 100644 --- a/graylog2-web-interface/src/views/components/contexts/MessageFavoriteFieldsContext.ts +++ b/graylog2-web-interface/src/views/components/contexts/MessageFavoriteFieldsContext.ts @@ -31,6 +31,7 @@ export type MessageFavoriteFieldsContextState = { message: Message; editableStreams: Array; setFieldsIsPending: boolean; + initialFavoriteFieldsByStream: Record>; }; const MessageFavoriteFieldsContext = React.createContext({ @@ -41,6 +42,7 @@ const MessageFavoriteFieldsContext = React.createContext MessageFavoriteFieldsContext);