1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

feat: group id clickable in event search (#10277)

Now when pressing the group id, the query params get updated.
Also the FilterItem appears and it is possible to discard the group id
selection through it.


![image](https://github.com/user-attachments/assets/83d5446f-4823-4c25-9fdc-870c2e4811d8)
This commit is contained in:
Jaanus Sellin 2025-07-02 14:16:40 +03:00 committed by GitHub
parent 1664c71b83
commit 30fbc62f9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 99 additions and 5 deletions

View File

@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
import { styled } from '@mui/material';
import type { EventSchema } from 'openapi';
import { useUiFlag } from 'hooks/useUiFlag';
import { useLocation } from 'react-router-dom';
interface IEventCardProps {
entry: EventSchema;
@ -74,17 +75,41 @@ export const StyledCodeSection = styled('div')(({ theme }) => ({
const EventCard = ({ entry }: IEventCardProps) => {
const { locationSettings } = useLocationSettings();
const eventGroupingEnabled = useUiFlag('eventGrouping');
const location = useLocation();
const createdAtFormatted = formatDateYMDHMS(
entry.createdAt,
locationSettings.locale,
);
const getGroupIdLink = () => {
const searchParams = new URLSearchParams(location.search);
searchParams.set('groupId', `IS:${entry.groupId}`);
return `${location.pathname}?${searchParams.toString()}`;
};
return (
<StyledContainerListItem>
<dl>
<StyledDefinitionTerm>Event id:</StyledDefinitionTerm>
<dd>{entry.id}</dd>
<ConditionallyRender
condition={
eventGroupingEnabled && entry.groupId !== undefined
}
show={
<>
<StyledDefinitionTerm>
Group id:
</StyledDefinitionTerm>
<dd>
<Link to={getGroupIdLink()}>
{entry.groupId}
</Link>
</dd>
</>
}
/>
<StyledDefinitionTerm>Changed at:</StyledDefinitionTerm>
<dd>{createdAtFormatted}</dd>
<StyledDefinitionTerm>Event:</StyledDefinitionTerm>

View File

@ -1,12 +1,23 @@
import { renderHook } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { useEventLogFilters } from './EventLogFilters.tsx';
const allFilterKeys = ['from', 'to', 'createdBy', 'type', 'project', 'feature'];
allFilterKeys.sort();
const renderWithRouter = (callback: () => any, initialEntries = ['/']) => {
return renderHook(callback, {
wrapper: ({ children }) => (
<MemoryRouter initialEntries={initialEntries}>
{children}
</MemoryRouter>
),
});
};
test('When you have no projects or flags, you should not get a project or flag filters', () => {
const { result } = renderHook(() => useEventLogFilters([], []));
const { result } = renderWithRouter(() => useEventLogFilters([], []));
const filterKeys = result.current.map((filter) => filter.filterKey);
filterKeys.sort();
@ -15,7 +26,7 @@ test('When you have no projects or flags, you should not get a project or flag f
});
test('When you have no projects, you should not get a project filter', () => {
const { result } = renderHook(() =>
const { result } = renderWithRouter(() =>
useEventLogFilters(
[],
// @ts-expect-error: omitting other properties we don't need
@ -29,7 +40,7 @@ test('When you have no projects, you should not get a project filter', () => {
});
test('When you have only one project, you should not get a project filter', () => {
const { result } = renderHook(() =>
const { result } = renderWithRouter(() =>
useEventLogFilters([{ id: 'a', name: 'A' }], []),
);
const filterKeys = result.current.map((filter) => filter.filterKey);
@ -39,7 +50,7 @@ test('When you have only one project, you should not get a project filter', () =
});
test('When you have two one project, you should not get a project filter', () => {
const { result } = renderHook(() =>
const { result } = renderWithRouter(() =>
useEventLogFilters(
[
{ id: 'a', name: 'A' },
@ -53,3 +64,20 @@ test('When you have two one project, you should not get a project filter', () =>
expect(filterKeys).toContain('project');
});
test('When groupId is in URL params, should include groupId filter', () => {
const { result } = renderWithRouter(
() => useEventLogFilters([], []),
['/?groupId=IS:123'],
);
const filterKeys = result.current.map((filter) => filter.filterKey);
expect(filterKeys).toContain('groupId');
});
test('When no groupId in URL params, should not include groupId filter', () => {
const { result } = renderWithRouter(() => useEventLogFilters([], []));
const filterKeys = result.current.map((filter) => filter.filterKey);
expect(filterKeys).not.toContain('groupId');
});

View File

@ -10,6 +10,8 @@ import { EventSchemaType, type FeatureSearchResponseSchema } from 'openapi';
import type { ProjectSchema } from 'openapi';
import { useEventCreators } from 'hooks/api/getters/useEventCreators/useEventCreators';
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
import { useLocation } from 'react-router-dom';
import { FilterItemParam } from 'utils/serializeQueryParams';
export const useEventLogFilters = (
projects: ProjectSchema[],
@ -17,9 +19,14 @@ export const useEventLogFilters = (
) => {
const { environments } = useEnvironments();
const { eventCreators } = useEventCreators();
const location = useLocation();
const [availableFilters, setAvailableFilters] = useState<IFilterItem[]>([]);
useEffect(() => {
const searchParams = new URLSearchParams(location.search);
const hasGroupId = searchParams.has('groupId');
const groupIdValue = searchParams.get('groupId');
const projectOptions =
projects?.map((project: ProjectSchema) => ({
label: project.name,
@ -50,6 +57,22 @@ export const useEventLogFilters = (
value: env.name,
})) ?? [];
const groupIdOptions =
hasGroupId && groupIdValue
? (() => {
const parsedGroupId =
FilterItemParam.decode(groupIdValue);
return parsedGroupId
? [
{
label: parsedGroupId.values[0],
value: parsedGroupId.values[0],
},
]
: [];
})()
: [];
const availableFilters: IFilterItem[] = [
{
label: 'Date From',
@ -87,6 +110,19 @@ export const useEventLogFilters = (
singularOperators: ['IS'],
pluralOperators: ['IS_ANY_OF'],
},
...(hasGroupId
? ([
{
label: 'Group ID',
icon: 'group',
options: groupIdOptions,
filterKey: 'groupId',
singularOperators: ['IS'],
pluralOperators: ['IS_ANY_OF'],
persistent: false,
},
] as IFilterItem[])
: []),
...(projectOptions.length > 1
? ([
{
@ -131,6 +167,7 @@ export const useEventLogFilters = (
JSON.stringify(projects),
JSON.stringify(eventCreators),
JSON.stringify(environments),
location.search,
]);
return availableFilters;

View File

@ -73,7 +73,7 @@ export const useEventLogSearch = (
type: FilterItemParam,
environment: FilterItemParam,
id: StringParam,
groupId: StringParam,
groupId: FilterItemParam,
...extraParameters(logType),
};

View File

@ -46,6 +46,10 @@ export interface EventSchema {
* @nullable
*/
ip?: string | null;
/**
* The event group ID.
*/
groupId?: string;
/**
* The concise, human-readable name of the event.
* @nullable