Browse Source

feat(trace-view-load-more): Added ui for loading longer traces. (#56476)

This PR adds a link-like button to the end of the trace view, allowing
user to load a trace view with more rows under the feature flag
`organizations:trace-view-load-more `.

Note: When it comes to very long traces (1500 -> 2000 rows), there is a
slight delay in between a Click to load more and the loader rendering.
This can potentially solved using react-virtualized library. This will
be a followup.


https://github.com/getsentry/sentry/assets/60121741/2cfba4d0-c11a-4561-838f-90b984caad62

---------

Co-authored-by: Abdullah Khan <abdullahkhan@PG9Y57YDXQ.local>
Co-authored-by: Nar Saynorath <nar.saynorath@sentry.io>
Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
Abdkhan14 1 year ago
parent
commit
5e45dbc028

+ 7 - 0
static/app/utils/performance/quickTrace/traceFullQuery.tsx

@@ -19,6 +19,7 @@ import {
 type AdditionalQueryProps = {
   detailed?: boolean;
   eventId?: string;
+  limit?: number;
 };
 
 type TraceFullQueryChildrenProps<T> = BaseTraceChildrenProps &
@@ -38,6 +39,7 @@ type QueryProps<T> = Omit<TraceRequestProps, 'eventView'> &
 function getTraceFullRequestPayload({
   detailed,
   eventId,
+  limit,
   ...props
 }: DiscoverQueryProps & AdditionalQueryProps) {
   const additionalApiPayload: any = getTraceRequestPayload(props);
@@ -45,6 +47,11 @@ function getTraceFullRequestPayload({
   if (eventId) {
     additionalApiPayload.event_id = eventId;
   }
+
+  if (limit) {
+    additionalApiPayload.limit = limit;
+  }
+
   return additionalApiPayload;
 }
 

+ 18 - 1
static/app/views/performance/traceDetails/content.tsx

@@ -51,6 +51,7 @@ type Props = Pick<RouteComponentProps<{traceSlug: string}, {}>, 'params' | 'loca
   traceEventView: EventView;
   traceSlug: string;
   traces: TraceFullDetailed[] | null;
+  handleLimitChange?: (newLimit: number) => void;
   orphanErrors?: TraceError[];
 };
 
@@ -132,7 +133,12 @@ class TraceDetailsContent extends Component<Props, State> {
   }
 
   renderTraceLoading() {
-    return <LoadingIndicator />;
+    return (
+      <LoadingContainer>
+        <StyledLoadingIndicator />
+        {t('Hang in there, as we build your trace view!')}
+      </LoadingContainer>
+    );
   }
 
   renderTraceRequiresDateRangeSelection() {
@@ -367,6 +373,7 @@ class TraceDetailsContent extends Component<Props, State> {
               traces={traces || []}
               meta={meta}
               orphanErrors={orphanErrors || []}
+              handleLimitChange={this.props.handleLimitChange}
             />
           </VisuallyCompleteWithData>
         </Margin>
@@ -414,6 +421,16 @@ class TraceDetailsContent extends Component<Props, State> {
   }
 }
 
+const StyledLoadingIndicator = styled(LoadingIndicator)`
+  margin-bottom: 0;
+`;
+
+const LoadingContainer = styled('div')`
+  font-size: ${p => p.theme.fontSizeLarge};
+  color: ${p => p.theme.subText};
+  text-align: center;
+`;
+
 const Margin = styled('div')`
   margin-top: ${space(2)};
 `;

+ 23 - 0
static/app/views/performance/traceDetails/index.tsx

@@ -23,6 +23,7 @@ import withApi from 'sentry/utils/withApi';
 import withOrganization from 'sentry/utils/withOrganization';
 
 import TraceDetailsContent from './content';
+import {DEFAULT_TRACE_ROWS_LIMIT} from './limitExceededMessage';
 import {getTraceSplitResults} from './utils';
 
 type Props = RouteComponentProps<{traceSlug: string}, {}> & {
@@ -30,7 +31,27 @@ type Props = RouteComponentProps<{traceSlug: string}, {}> & {
   organization: Organization;
 };
 
+type State = {
+  limit: number;
+};
+
 class TraceSummary extends Component<Props> {
+  state: State = {
+    limit: DEFAULT_TRACE_ROWS_LIMIT,
+  };
+
+  componentDidMount(): void {
+    const {query} = this.props.location;
+
+    if (query.limit) {
+      this.setState({limit: query.limit});
+    }
+  }
+
+  handleLimitChange = (newLimit: number) => {
+    this.setState({limit: newLimit});
+  };
+
   getDocumentTitle(): string {
     return [t('Trace Details'), t('Performance')].join(' — ');
   }
@@ -104,6 +125,7 @@ class TraceSummary extends Component<Props> {
           orphanErrors={orphanErrors}
           traces={transactions ?? (traces as TraceFullDetailed[])}
           meta={meta}
+          handleLimitChange={this.handleLimitChange}
         />
       );
     };
@@ -125,6 +147,7 @@ class TraceSummary extends Component<Props> {
         start={start}
         end={end}
         statsPeriod={statsPeriod}
+        limit={this.state.limit}
       >
         {traceResults => (
           <TraceMetaQuery

+ 67 - 12
static/app/views/performance/traceDetails/limitExceededMessage.tsx

@@ -1,3 +1,7 @@
+import {browserHistory} from 'react-router';
+import styled from '@emotion/styled';
+
+import {Button} from 'sentry/components/button';
 import DiscoverFeature from 'sentry/components/discover/discoverFeature';
 import Link from 'sentry/components/links/link';
 import {MessageRow} from 'sentry/components/performance/waterfall/messageRow';
@@ -5,6 +9,7 @@ import {t, tct} from 'sentry/locale';
 import {Organization} from 'sentry/types';
 import EventView from 'sentry/utils/discover/eventView';
 import {TraceMeta} from 'sentry/utils/performance/quickTrace/types';
+import {useLocation} from 'sentry/utils/useLocation';
 import {TraceInfo} from 'sentry/views/performance/traceDetails/types';
 
 interface LimitExceededMessageProps {
@@ -12,15 +17,22 @@ interface LimitExceededMessageProps {
   organization: Organization;
   traceEventView: EventView;
   traceInfo: TraceInfo;
+  handleLimitChange?: (newLimit: number) => void;
 }
+
+const MAX_TRACE_ROWS_LIMIT = 2000;
+export const DEFAULT_TRACE_ROWS_LIMIT = 100;
+
 function LimitExceededMessage({
   traceInfo,
   traceEventView,
   organization,
   meta,
+  handleLimitChange,
 }: LimitExceededMessageProps) {
   const count = traceInfo.transactions.size + traceInfo.errors.size;
   const totalEvents = (meta && meta.transactions + meta.errors) ?? count;
+  const location = useLocation();
 
   if (totalEvents === null || count >= totalEvents) {
     return null;
@@ -28,22 +40,65 @@ function LimitExceededMessage({
 
   const target = traceEventView.getResultsViewUrlTarget(organization.slug);
 
+  // Increment by by multiples of 500.
+  const increment = count <= 100 ? 400 : 500;
+  const currentLimit = location.query.limit
+    ? Number(location.query.limit)
+    : DEFAULT_TRACE_ROWS_LIMIT; // TODO Abdullah Khan: Use count when extra orphan row bug is fixed.
+
+  const discoverLink = (
+    <DiscoverFeature>
+      {({hasFeature}) => (
+        <StyledLink disabled={!hasFeature} to={target}>
+          {t('open in Discover')}
+        </StyledLink>
+      )}
+    </DiscoverFeature>
+  );
+
+  const limitExceededMessage = tct(
+    'Limited to a view of [count] rows. To view the full list, [discover].',
+    {
+      count,
+      discover: discoverLink,
+    }
+  );
+
+  const loadBiggerTraceMessage = tct(
+    'Click [loadMore:here] to build a view with more rows or to view the full list, [discover].',
+    {
+      loadMore: (
+        <Button
+          priority="link"
+          onClick={() => {
+            const newLimit = currentLimit + increment;
+            if (handleLimitChange) {
+              handleLimitChange(newLimit);
+            }
+            browserHistory.push({
+              pathname: location.pathname,
+              query: {...location.query, limit: newLimit},
+            });
+          }}
+          aria-label={t('Load more')}
+        />
+      ),
+      discover: discoverLink,
+    }
+  );
+
   return (
     <MessageRow>
-      {tct('Limited to a view of [count] rows. To view the full list, [discover].', {
-        count,
-        discover: (
-          <DiscoverFeature>
-            {({hasFeature}) => (
-              <Link disabled={!hasFeature} to={target}>
-                {t('Open in Discover')}
-              </Link>
-            )}
-          </DiscoverFeature>
-        ),
-      })}
+      {organization.features.includes('trace-view-load-more') &&
+      count < MAX_TRACE_ROWS_LIMIT
+        ? loadBiggerTraceMessage
+        : limitExceededMessage}
     </MessageRow>
   );
 }
 
+const StyledLink = styled(Link)`
+  margin-left: 0;
+`;
+
 export default LimitExceededMessage;

+ 3 - 0
static/app/views/performance/traceDetails/traceView.tsx

@@ -58,6 +58,7 @@ type Props = Pick<RouteComponentProps<{}, {}>, 'location'> & {
   traceSlug: string;
   traces: TraceFullDetailed[];
   filteredEventIds?: Set<string>;
+  handleLimitChange?: (newLimit: number) => void;
   orphanErrors?: TraceError[];
   traceInfo?: TraceInfo;
 };
@@ -139,6 +140,7 @@ export default function TraceView({
   traceEventView,
   filteredEventIds,
   orphanErrors,
+  handleLimitChange,
   ...props
 }: Props) {
   const sentryTransaction = Sentry.getCurrentHub().getScope()?.getTransaction();
@@ -454,6 +456,7 @@ export default function TraceView({
                     organization={organization}
                     traceEventView={traceEventView}
                     meta={meta}
+                    handleLimitChange={handleLimitChange}
                   />
                 </TraceViewContainer>
               </StyledTracePanel>

+ 14 - 6
static/app/views/performance/traceDetails/utils.tsx

@@ -11,6 +11,7 @@ import {
 } from 'sentry/utils/performance/quickTrace/types';
 import {isTraceSplitResult, reduceTrace} from 'sentry/utils/performance/quickTrace/utils';
 
+import {DEFAULT_TRACE_ROWS_LIMIT} from './limitExceededMessage';
 import {TraceInfo} from './types';
 
 export function getTraceDetailsUrl(
@@ -20,14 +21,21 @@ export function getTraceDetailsUrl(
   query: Query
 ): LocationDescriptor {
   const {start, end, statsPeriod} = dateSelection;
+
+  const queryParams = {
+    ...query,
+    statsPeriod,
+    [PAGE_URL_PARAM.PAGE_START]: start,
+    [PAGE_URL_PARAM.PAGE_END]: end,
+  };
+
+  if (organization.features.includes('trace-view-load-more')) {
+    queryParams.limit = DEFAULT_TRACE_ROWS_LIMIT;
+  }
+
   return {
     pathname: `/organizations/${organization.slug}/performance/trace/${traceSlug}/`,
-    query: {
-      ...query,
-      statsPeriod,
-      [PAGE_URL_PARAM.PAGE_START]: start,
-      [PAGE_URL_PARAM.PAGE_END]: end,
-    },
+    query: queryParams,
   };
 }