|
@@ -1,4 +1,4 @@
|
|
|
-import {Fragment, useCallback, useEffect, useMemo, useState} from 'react';
|
|
|
+import {Fragment, useCallback, useMemo, useState} from 'react';
|
|
|
import {useTheme} from '@emotion/react';
|
|
|
import styled from '@emotion/styled';
|
|
|
import debounce from 'lodash/debounce';
|
|
@@ -15,7 +15,6 @@ import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
|
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
|
|
|
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
|
|
|
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
|
|
|
-import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
|
|
|
import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
|
|
|
import Panel from 'sentry/components/panels/panel';
|
|
|
import PanelHeader from 'sentry/components/panels/panelHeader';
|
|
@@ -27,7 +26,6 @@ import {IconClose} from 'sentry/icons/iconClose';
|
|
|
import {IconWarning} from 'sentry/icons/iconWarning';
|
|
|
import {t, tct} from 'sentry/locale';
|
|
|
import {space} from 'sentry/styles/space';
|
|
|
-import type {PageFilters} from 'sentry/types/core';
|
|
|
import type {MetricAggregation, MRI} from 'sentry/types/metrics';
|
|
|
import type {Organization} from 'sentry/types/organization';
|
|
|
import {defined} from 'sentry/utils';
|
|
@@ -35,17 +33,20 @@ import {trackAnalytics} from 'sentry/utils/analytics';
|
|
|
import {browserHistory} from 'sentry/utils/browserHistory';
|
|
|
import {getUtcDateString} from 'sentry/utils/dates';
|
|
|
import {getFormattedMQL} from 'sentry/utils/metrics';
|
|
|
-import {useApiQuery} from 'sentry/utils/queryClient';
|
|
|
-import {decodeInteger, decodeList, decodeScalar} from 'sentry/utils/queryString';
|
|
|
+import {decodeInteger, decodeList} from 'sentry/utils/queryString';
|
|
|
import {useLocation} from 'sentry/utils/useLocation';
|
|
|
import useOrganization from 'sentry/utils/useOrganization';
|
|
|
import usePageFilters from 'sentry/utils/usePageFilters';
|
|
|
import useProjects from 'sentry/utils/useProjects';
|
|
|
import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout';
|
|
|
|
|
|
+import {usePageParams} from './hooks/usePageParams';
|
|
|
+import type {TraceResult} from './hooks/useTraces';
|
|
|
+import {useTraces} from './hooks/useTraces';
|
|
|
+import type {SpanResult} from './hooks/useTraceSpans';
|
|
|
+import {useTraceSpans} from './hooks/useTraceSpans';
|
|
|
import {type Field, FIELDS, SORTS} from './data';
|
|
|
import {
|
|
|
- BREAKDOWN_SLICES,
|
|
|
Description,
|
|
|
ProjectBadgeWrapper,
|
|
|
ProjectsRenderer,
|
|
@@ -72,27 +73,6 @@ const SPAN_PROPS_DOCS_URL =
|
|
|
'https://docs.sentry.io/concepts/search/searchable-properties/spans/';
|
|
|
const ONE_MINUTE = 60 * 1000; // in milliseconds
|
|
|
|
|
|
-function usePageParams(location) {
|
|
|
- const queries = useMemo(() => {
|
|
|
- return decodeList(location.query.query);
|
|
|
- }, [location.query.query]);
|
|
|
-
|
|
|
- const metricsMax = decodeScalar(location.query.metricsMax);
|
|
|
- const metricsMin = decodeScalar(location.query.metricsMin);
|
|
|
- const metricsOp = decodeScalar(location.query.metricsOp);
|
|
|
- const metricsQuery = decodeScalar(location.query.metricsQuery);
|
|
|
- const mri = decodeScalar(location.query.mri);
|
|
|
-
|
|
|
- return {
|
|
|
- queries,
|
|
|
- metricsMax,
|
|
|
- metricsMin,
|
|
|
- metricsOp,
|
|
|
- metricsQuery,
|
|
|
- mri,
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
export function Content() {
|
|
|
const location = useLocation();
|
|
|
|
|
@@ -592,227 +572,6 @@ function SpanRow({
|
|
|
);
|
|
|
}
|
|
|
|
|
|
-export type SpanResult<F extends string> = Record<F, any>;
|
|
|
-
|
|
|
-export interface TraceResult {
|
|
|
- breakdowns: TraceBreakdownResult[];
|
|
|
- duration: number;
|
|
|
- end: number;
|
|
|
- matchingSpans: number;
|
|
|
- name: string | null;
|
|
|
- numErrors: number;
|
|
|
- numOccurrences: number;
|
|
|
- numSpans: number;
|
|
|
- project: string | null;
|
|
|
- slices: number;
|
|
|
- start: number;
|
|
|
- trace: string;
|
|
|
-}
|
|
|
-
|
|
|
-interface TraceBreakdownBase {
|
|
|
- duration: number; // Contains the accurate duration for display. Start and end may be quantized.
|
|
|
- end: number;
|
|
|
- opCategory: string | null;
|
|
|
- sdkName: string | null;
|
|
|
- sliceEnd: number;
|
|
|
- sliceStart: number;
|
|
|
- sliceWidth: number;
|
|
|
- start: number;
|
|
|
-}
|
|
|
-
|
|
|
-type TraceBreakdownProject = TraceBreakdownBase & {
|
|
|
- kind: 'project';
|
|
|
- project: string;
|
|
|
-};
|
|
|
-
|
|
|
-type TraceBreakdownMissing = TraceBreakdownBase & {
|
|
|
- kind: 'missing';
|
|
|
- project: null;
|
|
|
-};
|
|
|
-
|
|
|
-export type TraceBreakdownResult = TraceBreakdownProject | TraceBreakdownMissing;
|
|
|
-
|
|
|
-interface TraceResults {
|
|
|
- data: TraceResult[];
|
|
|
- meta: any;
|
|
|
-}
|
|
|
-
|
|
|
-interface UseTracesOptions {
|
|
|
- datetime?: PageFilters['datetime'];
|
|
|
- enabled?: boolean;
|
|
|
- limit?: number;
|
|
|
- metricsMax?: string;
|
|
|
- metricsMin?: string;
|
|
|
- metricsOp?: string;
|
|
|
- metricsQuery?: string;
|
|
|
- mri?: string;
|
|
|
- query?: string | string[];
|
|
|
- sort?: '-timestamp';
|
|
|
-}
|
|
|
-
|
|
|
-function useTraces({
|
|
|
- datetime,
|
|
|
- enabled,
|
|
|
- limit,
|
|
|
- mri,
|
|
|
- metricsMax,
|
|
|
- metricsMin,
|
|
|
- metricsOp,
|
|
|
- metricsQuery,
|
|
|
- query,
|
|
|
- sort,
|
|
|
-}: UseTracesOptions) {
|
|
|
- const organization = useOrganization();
|
|
|
- const {projects} = useProjects();
|
|
|
- const {selection} = usePageFilters();
|
|
|
-
|
|
|
- const path = `/organizations/${organization.slug}/traces/`;
|
|
|
-
|
|
|
- const endpointOptions = {
|
|
|
- query: {
|
|
|
- project: selection.projects,
|
|
|
- environment: selection.environments,
|
|
|
- ...(datetime ?? normalizeDateTimeParams(selection.datetime)),
|
|
|
- query,
|
|
|
- sort,
|
|
|
- per_page: limit,
|
|
|
- breakdownSlices: BREAKDOWN_SLICES,
|
|
|
- mri,
|
|
|
- metricsMax,
|
|
|
- metricsMin,
|
|
|
- metricsOp,
|
|
|
- metricsQuery,
|
|
|
- },
|
|
|
- };
|
|
|
- const serializedEndpointOptions = JSON.stringify(endpointOptions);
|
|
|
-
|
|
|
- let queries: string[] = [];
|
|
|
- if (Array.isArray(query)) {
|
|
|
- queries = query;
|
|
|
- } else if (query !== undefined) {
|
|
|
- queries = [query];
|
|
|
- }
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- trackAnalytics('trace_explorer.search_request', {
|
|
|
- organization,
|
|
|
- queries,
|
|
|
- });
|
|
|
- // `queries` is already included as a dep in serializedEndpointOptions
|
|
|
- // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
- }, [serializedEndpointOptions, organization]);
|
|
|
-
|
|
|
- const result = useApiQuery<TraceResults>([path, endpointOptions], {
|
|
|
- staleTime: 0,
|
|
|
- refetchOnWindowFocus: false,
|
|
|
- retry: false,
|
|
|
- enabled,
|
|
|
- });
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- if (result.status === 'success') {
|
|
|
- const project_slugs = [...new Set(result.data.data.map(trace => trace.project))];
|
|
|
- const project_platforms = projects
|
|
|
- .filter(p => project_slugs.includes(p.slug))
|
|
|
- .map(p => p.platform ?? '');
|
|
|
-
|
|
|
- trackAnalytics('trace_explorer.search_success', {
|
|
|
- organization,
|
|
|
- queries,
|
|
|
- has_data: result.data.data.length > 0,
|
|
|
- num_traces: result.data.data.length,
|
|
|
- num_missing_trace_root: result.data.data.filter(trace => trace.name === null)
|
|
|
- .length,
|
|
|
- project_platforms,
|
|
|
- });
|
|
|
- } else if (result.status === 'error') {
|
|
|
- const response = result.error.responseJSON;
|
|
|
- const error =
|
|
|
- typeof response?.detail === 'string'
|
|
|
- ? response?.detail
|
|
|
- : response?.detail?.message;
|
|
|
- trackAnalytics('trace_explorer.search_failure', {
|
|
|
- organization,
|
|
|
- queries,
|
|
|
- error: error ?? '',
|
|
|
- });
|
|
|
- }
|
|
|
- // result.status is tied to result.data. No need to explicitly
|
|
|
- // include result.data as an additional dep.
|
|
|
- // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
- }, [serializedEndpointOptions, result.status, organization]);
|
|
|
-
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
-interface SpanResults<F extends string> {
|
|
|
- data: SpanResult<F>[];
|
|
|
- meta: any;
|
|
|
-}
|
|
|
-
|
|
|
-interface UseTraceSpansOptions<F extends string> {
|
|
|
- fields: F[];
|
|
|
- trace: TraceResult;
|
|
|
- datetime?: PageFilters['datetime'];
|
|
|
- enabled?: boolean;
|
|
|
- limit?: number;
|
|
|
- metricsMax?: string;
|
|
|
- metricsMin?: string;
|
|
|
- metricsOp?: string;
|
|
|
- metricsQuery?: string;
|
|
|
- mri?: string;
|
|
|
- query?: string | string[];
|
|
|
- sort?: string[];
|
|
|
-}
|
|
|
-
|
|
|
-function useTraceSpans<F extends string>({
|
|
|
- fields,
|
|
|
- trace,
|
|
|
- datetime,
|
|
|
- enabled,
|
|
|
- limit,
|
|
|
- mri,
|
|
|
- metricsMax,
|
|
|
- metricsMin,
|
|
|
- metricsOp,
|
|
|
- metricsQuery,
|
|
|
- query,
|
|
|
- sort,
|
|
|
-}: UseTraceSpansOptions<F>) {
|
|
|
- const organization = useOrganization();
|
|
|
- const {selection} = usePageFilters();
|
|
|
-
|
|
|
- const path = `/organizations/${organization.slug}/trace/${trace.trace}/spans/`;
|
|
|
-
|
|
|
- const endpointOptions = {
|
|
|
- query: {
|
|
|
- project: selection.projects,
|
|
|
- environment: selection.environments,
|
|
|
- ...(datetime ?? normalizeDateTimeParams(selection.datetime)),
|
|
|
- field: fields,
|
|
|
- query,
|
|
|
- sort,
|
|
|
- per_page: limit,
|
|
|
- breakdownSlices: BREAKDOWN_SLICES,
|
|
|
- maxSpansPerTrace: 10,
|
|
|
- mri,
|
|
|
- metricsMax,
|
|
|
- metricsMin,
|
|
|
- metricsOp,
|
|
|
- metricsQuery,
|
|
|
- },
|
|
|
- };
|
|
|
-
|
|
|
- const result = useApiQuery<SpanResults<F>>([path, endpointOptions], {
|
|
|
- staleTime: 0,
|
|
|
- refetchOnWindowFocus: false,
|
|
|
- retry: false,
|
|
|
- enabled,
|
|
|
- });
|
|
|
-
|
|
|
- return result;
|
|
|
-}
|
|
|
-
|
|
|
const LayoutMain = styled(Layout.Main)`
|
|
|
display: flex;
|
|
|
flex-direction: column;
|