Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions static/app/views/explore/logs/logsSidebarContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {createContext, useContext, useMemo} from 'react';

interface LogsSidebarContextValue {
setSidebarOpen: (open: boolean) => void;
sidebarOpen: boolean;
}

const LogsSidebarContext = createContext<LogsSidebarContextValue | null>(null);

interface LogsSidebarProviderProps {
children: React.ReactNode;
setSidebarOpen: (open: boolean) => void;
sidebarOpen: boolean;
}

export function LogsSidebarProvider({
children,
sidebarOpen,
setSidebarOpen,
}: LogsSidebarProviderProps) {
const value = useMemo(
() => ({sidebarOpen, setSidebarOpen}),
[sidebarOpen, setSidebarOpen]
);
return (
<LogsSidebarContext.Provider value={value}>{children}</LogsSidebarContext.Provider>
);
}

/**
* Returns the logs sidebar context, or null when there is no surrounding
* `LogsSidebarProvider` (e.g. embedded usages outside the logs tab).
*/
export function useLogsSidebar() {
return useContext(LogsSidebarContext);
}
7 changes: 4 additions & 3 deletions static/app/views/explore/logs/logsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Fragment, memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
import styled from '@emotion/styled';
import {useQueryClient} from '@tanstack/react-query';

Expand Down Expand Up @@ -58,6 +58,7 @@ import {LogsExportSwitch} from 'sentry/views/explore/logs/exports/logsExportSwit
import {AutorefreshToggle} from 'sentry/views/explore/logs/logsAutoRefresh';
import {LogsDownSamplingAlert} from 'sentry/views/explore/logs/logsDownsamplingAlert';
import {LogsGraph} from 'sentry/views/explore/logs/logsGraph';
import {LogsSidebarProvider} from 'sentry/views/explore/logs/logsSidebarContext';
import {LogsTabSeerComboBox} from 'sentry/views/explore/logs/logsTabSeerComboBox';
import {LogsToolbar} from 'sentry/views/explore/logs/logsToolbar';
import {
Expand Down Expand Up @@ -444,7 +445,7 @@ function LogsTabContentInner({datePageFilterProps, tableExpando}: LogsTabProps)
const {infiniteLogsQueryResult} = useLogsPageData();

return (
<Fragment>
<LogsSidebarProvider sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen}>
<LogsSearchSection
datePageFilterProps={datePageFilterProps}
searchBarWidthOffset={columnEditorButtonRef.current?.clientWidth}
Expand Down Expand Up @@ -553,7 +554,7 @@ function LogsTabContentInner({datePageFilterProps, tableExpando}: LogsTabProps)
</ExploreContentSection>
</ViewportConstrainedBody>
</ViewportConstrainedPage>
</Fragment>
</LogsSidebarProvider>
);
}

Expand Down
146 changes: 145 additions & 1 deletion static/app/views/explore/logs/tables/logsTableRow.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ import {
import {PageFiltersStore} from 'sentry/components/pageFilters/store';
import {ProjectsStore} from 'sentry/stores/projectsStore';
import {LogsAnalyticsPageSource} from 'sentry/utils/analytics/logsAnalyticsEvent';
import {LOGS_FIELDS_KEY} from 'sentry/views/explore/contexts/logs/logsPageParams';
import {
LOGS_FIELDS_KEY,
LOGS_GROUP_BY_KEY,
} from 'sentry/views/explore/contexts/logs/logsPageParams';
import {LOGS_SORT_BYS_KEY} from 'sentry/views/explore/contexts/logs/sortBys';
import {type TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails';
import {DEFAULT_TRACE_ITEM_HOVER_TIMEOUT} from 'sentry/views/explore/logs/constants';
import {LogsQueryParamsProvider} from 'sentry/views/explore/logs/logsQueryParamsProvider';
import {LogsSidebarProvider} from 'sentry/views/explore/logs/logsSidebarContext';
import {LogRowContent} from 'sentry/views/explore/logs/tables/logsTableRow';
import {OurLogKnownFieldKey} from 'sentry/views/explore/logs/types';

Expand Down Expand Up @@ -577,6 +581,146 @@ describe('logsTableRow', () => {
expect(copiedUrl).toContain('logsQuery=id%3A1');
});

it('adds a group by from the attribute actions menu', async () => {
const {router} = render(
<LogRowContent
dataRow={rowData}
highlightTerms={[]}
meta={LogFixtureMeta(rowData)}
sharedHoverTimeoutRef={{
current: null,
}}
/>,
{organization, initialRouterConfig, additionalWrapper: ProviderWrapper}
);

const logTableRow = await screen.findByTestId('log-table-row');
await userEvent.click(logTableRow);

await waitFor(() => {
expect(rowDetailsMock).toHaveBeenCalledTimes(1);
});

const severityRow = await screen.findByTestId('tree-key-severity');
const attributeTreeRow = severityRow.closest('[data-test-id="attribute-tree-row"]')!;
expect(attributeTreeRow).toBeInTheDocument();

await userEvent.hover(attributeTreeRow);
await userEvent.click(
within(attributeTreeRow as HTMLElement).getByRole('button', {
name: 'Attribute Actions Menu',
})
);

const groupByItem = await screen.findByRole('menuitemradio', {name: 'Group by'});
await userEvent.click(groupByItem);

expect(router.location.query).toEqual(
expect.objectContaining({
mode: 'aggregate',
aggregateField: expect.arrayContaining(['{"groupBy":"severity"}']),
})
);
});

it('opens the sidebar when group by is clicked', async () => {
const setSidebarOpen = jest.fn();

function SidebarWrapper({children}: {children?: React.ReactNode}) {
return (
<LogsSidebarProvider sidebarOpen={false} setSidebarOpen={setSidebarOpen}>
<LogsQueryParamsProvider
analyticsPageSource={LogsAnalyticsPageSource.EXPLORE_LOGS}
source="location"
>
<table>
<tbody>{children}</tbody>
</table>
</LogsQueryParamsProvider>
</LogsSidebarProvider>
);
}

render(
<LogRowContent
dataRow={rowData}
highlightTerms={[]}
meta={LogFixtureMeta(rowData)}
sharedHoverTimeoutRef={{
current: null,
}}
/>,
{organization, initialRouterConfig, additionalWrapper: SidebarWrapper}
);

const logTableRow = await screen.findByTestId('log-table-row');
await userEvent.click(logTableRow);

await waitFor(() => {
expect(rowDetailsMock).toHaveBeenCalledTimes(1);
});

const severityRow = await screen.findByTestId('tree-key-severity');
const attributeTreeRow = severityRow.closest('[data-test-id="attribute-tree-row"]')!;
await userEvent.hover(attributeTreeRow);
await userEvent.click(
within(attributeTreeRow as HTMLElement).getByRole('button', {
name: 'Attribute Actions Menu',
})
);

await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Group by'}));

expect(setSidebarOpen).toHaveBeenCalledWith(true);
});

it('disables the group by menu item when the attribute is already grouped by', async () => {
render(
<LogRowContent
dataRow={rowData}
highlightTerms={[]}
meta={LogFixtureMeta(rowData)}
sharedHoverTimeoutRef={{
current: null,
}}
/>,
{
organization,
initialRouterConfig: {
...initialRouterConfig,
location: {
...initialRouterConfig.location,
query: {
...initialRouterConfig.location.query,
mode: 'aggregate',
[LOGS_GROUP_BY_KEY]: 'severity',
},
},
},
additionalWrapper: ProviderWrapper,
}
);

const logTableRow = await screen.findByTestId('log-table-row');
await userEvent.click(logTableRow);

await waitFor(() => {
expect(rowDetailsMock).toHaveBeenCalledTimes(1);
});

const severityRow = await screen.findByTestId('tree-key-severity');
const attributeTreeRow = severityRow.closest('[data-test-id="attribute-tree-row"]')!;
await userEvent.hover(attributeTreeRow);
await userEvent.click(
within(attributeTreeRow as HTMLElement).getByRole('button', {
name: 'Attribute Actions Menu',
})
);

const groupByItem = await screen.findByRole('menuitemradio', {name: 'Group by'});
expect(groupByItem).toHaveAttribute('aria-disabled', 'true');
});

it('does not toggle row when clicking cell action menu items', async () => {
const mockWriteText = jest.fn().mockResolvedValue(undefined);
Object.defineProperty(window.navigator, 'clipboard', {
Expand Down
34 changes: 34 additions & 0 deletions static/app/views/explore/logs/useLogAttributesTreeActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ import {useCallback} from 'react';
import type {MenuItemProps} from 'sentry/components/dropdownMenu';
import {t} from 'sentry/locale';
import type {AttributesTreeContent} from 'sentry/views/explore/components/traceItemAttributes/attributesTree';
import {useLogsSidebar} from 'sentry/views/explore/logs/logsSidebarContext';
import {OurLogKnownFieldKey} from 'sentry/views/explore/logs/types';
import {
useQueryParamsFields,
useQueryParamsGroupBys,
useQueryParamsSearch,
useSetQueryParamsFields,
useSetQueryParamsGroupBys,
useSetQueryParamsSearch,
} from 'sentry/views/explore/queryParams/context';
import {Mode} from 'sentry/views/explore/queryParams/mode';

export function useLogAttributesTreeActions({embedded}: {embedded: boolean}) {
const setLogsSearch = useSetQueryParamsSearch();
const search = useQueryParamsSearch();
const fields = useQueryParamsFields();
const setLogFields = useSetQueryParamsFields();
const groupBys = useQueryParamsGroupBys();
const setGroupBys = useSetQueryParamsGroupBys();
const sidebar = useLogsSidebar();

const addSearchFilter = useCallback(
(content: AttributesTreeContent, negated?: boolean) => {
Expand Down Expand Up @@ -50,6 +57,26 @@ export function useLogAttributesTreeActions({embedded}: {embedded: boolean}) {
[setLogFields, fields]
);

const addGroupBy = useCallback(
(content: AttributesTreeContent) => {
const originalAttribute = content.originalAttribute;
if (!originalAttribute) {
return;
}
const key = originalAttribute.original_attribute_key;
// Drop empty placeholder group bys, dedupe, then append the new key.
const newGroupBys = groupBys.filter(Boolean);
if (!newGroupBys.includes(key)) {
newGroupBys.push(key);
}
setGroupBys(newGroupBys, Mode.AGGREGATE);
// Reveal the Visualize / Group By controls so the user can see the
// grouping they just added.
sidebar?.setSidebarOpen(true);
},
[setGroupBys, groupBys, sidebar]
);

return (content: AttributesTreeContent) => {
if (!content.originalAttribute) {
return [];
Expand All @@ -73,6 +100,13 @@ export function useLogAttributesTreeActions({embedded}: {embedded: boolean}) {
disabled: fields.includes(content.originalAttribute.original_attribute_key),
onAction: () => addColumn(content),
},
{
key: 'add-group-by',
label: t('Group by'),
hidden: embedded,
disabled: groupBys.includes(content.originalAttribute.original_attribute_key),
onAction: () => addGroupBy(content),
},
];

return items;
Expand Down
Loading