diff --git a/static/app/utils/useResettableState.tsx b/static/app/utils/useResettableState.tsx new file mode 100644 index 00000000000000..5ae651bc1aea7a --- /dev/null +++ b/static/app/utils/useResettableState.tsx @@ -0,0 +1,20 @@ +import {useCallback, useRef, useState} from 'react'; + +import {defined} from 'sentry/utils'; + +export function useResettableState(defaultValue: () => T) { + const defaultValueBoxed = useRef(defaultValue); + defaultValueBoxed.current = defaultValue; + + const [state, _setState] = useState(defaultValueBoxed.current()); + + const setState = useCallback((newState: T | null | undefined) => { + if (defined(newState)) { + _setState(newState); + } else if (newState === null) { + _setState(defaultValueBoxed.current()); + } + }, []); + + return [state, setState] as const; +} diff --git a/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx b/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx index 13e908649176fc..14b5407b6aa3f1 100644 --- a/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx +++ b/static/app/views/explore/components/traceItemSearchQueryBuilder.tsx @@ -212,6 +212,9 @@ function itemTypeToRecentSearches(itemType: TraceItemDataset) { if (itemType === TraceItemDataset.SPANS) { return SavedSearchType.SPAN; } + if (itemType === TraceItemDataset.TRACEMETRICS) { + return SavedSearchType.METRIC; + } return SavedSearchType.LOG; } @@ -219,6 +222,9 @@ function itemTypeToFilterKeySections(itemType: TraceItemDataset) { if (itemType === TraceItemDataset.SPANS) { return SPANS_FILTER_KEY_SECTIONS; } + if (itemType === TraceItemDataset.TRACEMETRICS) { + return []; + } return LOGS_FILTER_KEY_SECTIONS; } @@ -226,5 +232,8 @@ function itemTypeToDefaultPlaceholder(itemType: TraceItemDataset) { if (itemType === TraceItemDataset.SPANS) { return t('Search for spans, users, tags, and more'); } + if (itemType === TraceItemDataset.TRACEMETRICS) { + return t('Search for metrics, users, tags, and more'); + } return t('Search for logs, users, tags, and more'); } diff --git a/static/app/views/explore/logs/logsStateQueryParamsProvider.tsx b/static/app/views/explore/logs/logsStateQueryParamsProvider.tsx index 75dd6ec070d536..3cb2a27d7a7683 100644 --- a/static/app/views/explore/logs/logsStateQueryParamsProvider.tsx +++ b/static/app/views/explore/logs/logsStateQueryParamsProvider.tsx @@ -1,7 +1,7 @@ import type {ReactNode} from 'react'; import {useCallback, useMemo, useState} from 'react'; -import {defined} from 'sentry/utils'; +import {useResettableState} from 'sentry/utils/useResettableState'; import {defaultLogFields} from 'sentry/views/explore/contexts/logs/fields'; import {defaultSortBys} from 'sentry/views/explore/contexts/pageParamsContext/sortBys'; import { @@ -91,20 +91,3 @@ export function LogsStateQueryParamsProvider({ function defaultAggregateFields() { return [...defaultGroupBys(), ...defaultVisualizes()]; } - -function useResettableState(defaultValue: () => T) { - const [state, _setState] = useState(defaultValue()); - - const setState = useCallback( - (newState: T | null | undefined) => { - if (defined(newState)) { - _setState(newState); - } else if (newState === null) { - _setState(defaultValue()); - } - }, - [defaultValue] - ); - - return [state, setState] as const; -} diff --git a/static/app/views/explore/logs/styles.tsx b/static/app/views/explore/logs/styles.tsx index b8479366723703..09ed609d9bbdcc 100644 --- a/static/app/views/explore/logs/styles.tsx +++ b/static/app/views/explore/logs/styles.tsx @@ -390,7 +390,7 @@ export const TopSectionBody = styled(Body)` } `; -export const BottomSectionBody = styled('div')<{sidebarOpen: boolean}>` +export const BottomSectionBody = styled('div')<{sidebarOpen?: boolean}>` flex: 1; padding: ${space(1)} ${space(2)} ${space(3)} ${space(2)}; background-color: ${p => p.theme.backgroundSecondary}; diff --git a/static/app/views/explore/metrics/metricRow.tsx b/static/app/views/explore/metrics/metricRow.tsx new file mode 100644 index 00000000000000..55c3cb4ea7147e --- /dev/null +++ b/static/app/views/explore/metrics/metricRow.tsx @@ -0,0 +1,72 @@ +import {useMemo} from 'react'; + +import {SearchQueryBuilderProvider} from 'sentry/components/searchQueryBuilder/context'; +import { + TraceItemSearchQueryBuilder, + useSearchQueryBuilderProps, + type TraceItemSearchQueryBuilderProps, +} from 'sentry/views/explore/components/traceItemSearchQueryBuilder'; +import {useMetricVisualize} from 'sentry/views/explore/metrics/metricsQueryParams'; +import {type TraceMetric} from 'sentry/views/explore/metrics/traceMetric'; +import { + useQueryParamsGroupBys, + useQueryParamsQuery, + useSetQueryParamsQuery, +} from 'sentry/views/explore/queryParams/context'; +import {TraceItemDataset} from 'sentry/views/explore/types'; + +interface MetricRowProps { + traceMetric: TraceMetric; +} + +export function MetricRow({traceMetric}: MetricRowProps) { + const query = useQueryParamsQuery(); + const setQuery = useSetQueryParamsQuery(); + + const tracesItemSearchQueryBuilderProps: TraceItemSearchQueryBuilderProps = + useMemo(() => { + return { + itemType: TraceItemDataset.TRACEMETRICS, + numberAttributes: {}, + stringAttributes: {}, + numberSecondaryAliases: {}, + stringSecondaryAliases: {}, + initialQuery: query, + onSearch: setQuery, + searchSource: 'tracemetrics', + }; + }, [query, setQuery]); + + const searchQueryBuilderProviderProps = useSearchQueryBuilderProps( + tracesItemSearchQueryBuilderProps + ); + + return ( + + + + ); +} + +interface MetricToolbarProps { + traceMetric: TraceMetric; + tracesItemSearchQueryBuilderProps: TraceItemSearchQueryBuilderProps; +} + +function MetricToolbar({ + tracesItemSearchQueryBuilderProps, + traceMetric, +}: MetricToolbarProps) { + const visualize = useMetricVisualize(); + const groupBys = useQueryParamsGroupBys(); + const query = useQueryParamsQuery(); + return ( +
+ {traceMetric.name}/{visualize.yAxis}/ by {groupBys.join(',')}/ where {query} + +
+ ); +} diff --git a/static/app/views/explore/metrics/metricsQueryParams.tsx b/static/app/views/explore/metrics/metricsQueryParams.tsx new file mode 100644 index 00000000000000..8f29e1319ded1e --- /dev/null +++ b/static/app/views/explore/metrics/metricsQueryParams.tsx @@ -0,0 +1,62 @@ +import type {ReactNode} from 'react'; +import {useCallback, useMemo} from 'react'; + +import {useResettableState} from 'sentry/utils/useResettableState'; +import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode'; +import { + QueryParamsContextProvider, + useQueryParamsVisualizes, +} from 'sentry/views/explore/queryParams/context'; +import {ReadableQueryParams} from 'sentry/views/explore/queryParams/readableQueryParams'; +import {VisualizeFunction} from 'sentry/views/explore/queryParams/visualize'; +import type {WritableQueryParams} from 'sentry/views/explore/queryParams/writableQueryParams'; + +interface MetricsQueryParamsProviderProps { + children: ReactNode; +} + +export function MetricsQueryParamsProvider({children}: MetricsQueryParamsProviderProps) { + const [query, setQuery] = useResettableState(() => ''); + + const readableQueryParams = useMemo(() => { + return new ReadableQueryParams({ + extrapolate: true, + mode: Mode.AGGREGATE, + query, + + cursor: '', + fields: ['id', 'timestamp'], + sortBys: [{field: 'timestamp', kind: 'desc'}], + + aggregateCursor: '', + aggregateFields: [new VisualizeFunction('sum(value)')], + aggregateSortBys: [{field: 'sum(value)', kind: 'desc'}], + }); + }, [query]); + + const setWritableQueryParams = useCallback( + (writableQueryParams: WritableQueryParams) => { + setQuery(writableQueryParams.query); + }, + [setQuery] + ); + + return ( + + {children} + + ); +} + +export function useMetricVisualize() { + const visualizes = useQueryParamsVisualizes(); + if (visualizes.length === 1) { + return visualizes[0]!; + } + throw new Error('Only 1 visualize per metric allowed'); +} diff --git a/static/app/views/explore/metrics/metricsTab.tsx b/static/app/views/explore/metrics/metricsTab.tsx index 156855400f691f..1c51fcaac7b068 100644 --- a/static/app/views/explore/metrics/metricsTab.tsx +++ b/static/app/views/explore/metrics/metricsTab.tsx @@ -1,17 +1,19 @@ +import {Fragment, useMemo} from 'react'; + import * as Layout from 'sentry/components/layouts/thirds'; import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; -import {SearchQueryBuilderProvider} from 'sentry/components/searchQueryBuilder/context'; import {t} from 'sentry/locale'; -import {useSearchQueryBuilderProps} from 'sentry/views/explore/components/traceItemSearchQueryBuilder'; import { BottomSectionBody, FilterBarContainer, StyledPageFilterBar, TopSectionBody, } from 'sentry/views/explore/logs/styles'; -import {TraceItemDataset} from 'sentry/views/explore/types'; +import {MetricRow} from 'sentry/views/explore/metrics/metricRow'; +import {MetricsQueryParamsProvider} from 'sentry/views/explore/metrics/metricsQueryParams'; +import {type TraceMetric} from 'sentry/views/explore/metrics/traceMetric'; import type {PickableDays} from 'sentry/views/explore/utils'; type LogsTabProps = PickableDays; @@ -21,34 +23,58 @@ export function MetricsTabContent({ maxPickableDays, relativeOptions, }: LogsTabProps) { - const searchQueryBuilderProviderProps = useSearchQueryBuilderProps({ - itemType: TraceItemDataset.TRACEMETRICS, - numberAttributes: {}, - stringAttributes: {}, - numberSecondaryAliases: {}, - stringSecondaryAliases: {}, - initialQuery: '', - searchSource: 'ourmetrics', - }); return ( - - - - - - - - - - - - - Currently in development - + + + + + ); +} + +function MetricsTabFilterSection({ + defaultPeriod, + maxPickableDays, + relativeOptions, +}: PickableDays) { + return ( + + + + + + + + + + + + ); +} + +function MetricsTabBodySection() { + const traceMetrics: TraceMetric[] = useMemo(() => { + return [{name: 'myfirstmetric'}, {name: 'mysecondmetric'}]; + }, []); + + return ( + + {traceMetrics.map((traceMetric, index) => { + return ( + // TODO: figure out a better `key` + + + + ); + })} + ); } diff --git a/static/app/views/explore/metrics/traceMetric.tsx b/static/app/views/explore/metrics/traceMetric.tsx new file mode 100644 index 00000000000000..8126d97611c13b --- /dev/null +++ b/static/app/views/explore/metrics/traceMetric.tsx @@ -0,0 +1,3 @@ +export interface TraceMetric { + name: string; +}