Skip to content

Commit

Permalink
feat(metrics): Add guides (#71018)
Browse files Browse the repository at this point in the history
https://github.com/getsentry/sentry/assets/9060071/6a5db38c-50ab-4d8a-a40e-458dcb12261c

The last step has nicer text if you have Trace Explorer—we add there
that you can filter by span-specific tags.

Closes #70212

---------

Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
  • Loading branch information
matejminar and getsantry[bot] committed May 16, 2024
1 parent c36eade commit f89be43
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 67 deletions.
59 changes: 57 additions & 2 deletions static/app/components/assistant/getGuidesContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import ExternalLink from 'sentry/components/links/externalLink';
import Link from 'sentry/components/links/link';
import {t, tct} from 'sentry/locale';
import ConfigStore from 'sentry/stores/configStore';
import type {Organization} from 'sentry/types';

export default function getGuidesContent(orgSlug: string | null): GuidesContent {
export default function getGuidesContent(
organization: Organization | null
): GuidesContent {
if (ConfigStore.get('demoMode')) {
return getDemoModeGuides();
}
Expand Down Expand Up @@ -90,7 +93,11 @@ export default function getGuidesContent(orgSlug: string | null): GuidesContent
description: tct(
`Today only admins in your organization can create alert rules but we recommend [link:allowing members to create alerts], too.`,
{
link: <Link to={orgSlug ? `/settings/${orgSlug}` : `/settings`} />,
link: (
<Link
to={organization?.slug ? `/settings/${organization.slug}` : `/settings`}
/>
),
}
),
nextText: t('Allow'),
Expand Down Expand Up @@ -174,6 +181,54 @@ export default function getGuidesContent(orgSlug: string | null): GuidesContent
},
],
},
{
guide: 'metrics_onboarding',
requiredTargets: ['metrics_onboarding'],
steps: [
{
title: t('Metrics Selector'),
target: 'metrics_selector',
description: t('Your metrics are available here.'),
},
{
title: t('Aggregate Metrics'),
target: 'metrics_aggregate',
description: t('See different facets of your metric through aggregations.'),
},
{
title: t('Grouping & Filtering'),
target: 'metrics_groupby',
description: t('Segment or filter your data by the tags you’ve attached.'),
},
{
title: t('Multiple Metrics'),
target: 'add_metric_query',
description: t('Plot a second metric to see correlations.'),
},
{
title: t('Visualization'),
target: 'metrics_chart',
description: t(
'View plotted metrics, dots on the chart represent associated sample spans.'
),
},
{
title: t('Span Samples'),
target: 'metrics_table',
description: tct(
'See sample spans summarized in a table format. [openInTraces]',
{
openInTraces:
organization?.features.includes(
'performance-trace-explorer-with-metrics'
) && organization?.features.includes('performance-trace-explorer')
? t('To filter by tags found only on spans, click "Open in Traces".')
: '',
}
),
},
],
},
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export function Queries({
<ExpressionFormWrapper>
<ExpressionFormRowWrapper>
<QueryBuilder
index={index}
onChange={data => onQueryChange(data, index)}
metricsQuery={query}
projects={selection.projects}
Expand Down
2 changes: 1 addition & 1 deletion static/app/stores/guideStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const storeConfig: GuideStoreDefinition = {
return;
}

const guidesContent: GuidesContent = getGuidesContent(this.state.orgSlug);
const guidesContent: GuidesContent = getGuidesContent(this.state.organization);
// map server guide state (i.e. seen status) with guide content
const guides = guidesContent.reduce((acc: Guide[], content) => {
const serverGuide = data.find(guide => guide.guide === content.guide);
Expand Down
2 changes: 2 additions & 0 deletions static/app/views/metrics/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as Sentry from '@sentry/react';
import emptyStateImg from 'sentry-images/spot/custom-metrics-empty-state.svg';

import Alert from 'sentry/components/alert';
import GuideAnchor from 'sentry/components/assistant/guideAnchor';
import FeatureBadge from 'sentry/components/badge/featureBadge';
import Banner from 'sentry/components/banner';
import {Button} from 'sentry/components/button';
Expand Down Expand Up @@ -148,6 +149,7 @@ export const MetricsLayout = memo(() => {
<LoadingIndicator />
) : hasCustomMetrics || isEmptyStateDismissed ? (
<Fragment>
<GuideAnchor target="metrics_onboarding" />
<Queries />
<MetricScratchpad />
<WidgetDetails />
Expand Down
18 changes: 11 additions & 7 deletions static/app/views/metrics/queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Fragment, useCallback, useLayoutEffect, useMemo} from 'react';
import styled from '@emotion/styled';
import * as echarts from 'echarts/core';

import GuideAnchor from 'sentry/components/assistant/guideAnchor';
import {Button} from 'sentry/components/button';
import SwitchButton from 'sentry/components/switchButton';
import {Tooltip} from 'sentry/components/tooltip';
Expand Down Expand Up @@ -118,13 +119,15 @@ export function Queries() {
))}
</Wrapper>
<ButtonBar addQuerySymbolSpacing={showQuerySymbols}>
<Button
size="sm"
icon={<IconAdd isCircled />}
onClick={() => handleAddWidget(MetricExpressionType.QUERY)}
>
{t('Add metric')}
</Button>
<GuideAnchor target="add_metric_query" position="bottom">
<Button
size="sm"
icon={<IconAdd isCircled />}
onClick={() => handleAddWidget(MetricExpressionType.QUERY)}
>
{t('Add metric')}
</Button>
</GuideAnchor>
<Button
size="sm"
icon={<IconAdd isCircled />}
Expand Down Expand Up @@ -207,6 +210,7 @@ function Query({
/>
)}
<QueryBuilder
index={index}
onChange={handleChange}
metricsQuery={metricsQuery}
projects={projects}
Expand Down
105 changes: 59 additions & 46 deletions static/app/views/metrics/queryBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Fragment, memo, useCallback, useEffect, useMemo, useState} from 'react';
import styled from '@emotion/styled';
import uniqBy from 'lodash/uniqBy';

import GuideAnchor from 'sentry/components/assistant/guideAnchor';
import {ComboBox} from 'sentry/components/comboBox';
import type {ComboBoxOption} from 'sentry/components/comboBox/types';
import type {SelectOption} from 'sentry/components/compactSelect';
Expand Down Expand Up @@ -35,6 +36,7 @@ import {MetricListItemDetails} from 'sentry/views/metrics/metricListItemDetails'
import {MetricSearchBar} from 'sentry/views/metrics/metricSearchBar';

type QueryBuilderProps = {
index: number;
metricsQuery: MetricsQuery;
onChange: (data: Partial<MetricsQuery>) => void;
projects: number[];
Expand Down Expand Up @@ -73,6 +75,7 @@ export const QueryBuilder = memo(function QueryBuilder({
metricsQuery,
projects: projectIds,
onChange,
index,
}: QueryBuilderProps) {
const organization = useOrganization();
const pageFilters = usePageFilters();
Expand Down Expand Up @@ -248,54 +251,64 @@ export const QueryBuilder = memo(function QueryBuilder({
return (
<QueryBuilderWrapper>
<FlexBlock>
<MetricComboBox
aria-label={t('Metric')}
placeholder={t('Select a metric')}
loadingMessage={t('Loading metrics...')}
sizeLimit={100}
size="md"
menuSize="sm"
isLoading={isMetaLoading}
onOpenChange={handleOpenMetricsMenu}
options={mriOptions}
value={metricsQuery.mri}
onChange={handleMRIChange}
growingInput
menuWidth="450px"
/>
<FlexBlock>
<OpSelect
size="md"
triggerProps={{prefix: t('Agg')}}
options={
selectedMeta?.operations.filter(isAllowedOp).map(op => ({
label: op,
value: op,
})) ?? []
}
triggerLabel={metricsQuery.op}
disabled={!selectedMeta}
value={metricsQuery.op}
onChange={handleOpChange}
/>
<CompactSelect
multiple
<GuideAnchor target="metrics_selector" position="bottom" disabled={index !== 0}>
<MetricComboBox
aria-label={t('Metric')}
placeholder={t('Select a metric')}
loadingMessage={t('Loading metrics...')}
sizeLimit={100}
size="md"
triggerProps={{prefix: t('Group by')}}
options={groupByOptions.map(tag => ({
label: tag.key,
value: tag.key,
trailingItems: tag.trailingItems ?? (
<Fragment>
{tag.key === 'release' && <IconReleases size="xs" />}
{tag.key === 'transaction' && <IconLightning size="xs" />}
</Fragment>
),
}))}
disabled={!metricsQuery.mri || tagsIsLoading}
value={metricsQuery.groupBy}
onChange={handleGroupByChange}
menuSize="sm"
isLoading={isMetaLoading}
onOpenChange={handleOpenMetricsMenu}
options={mriOptions}
value={metricsQuery.mri}
onChange={handleMRIChange}
growingInput
menuWidth="450px"
/>
</GuideAnchor>
<FlexBlock>
<GuideAnchor
target="metrics_aggregate"
position="bottom"
disabled={index !== 0}
>
<OpSelect
size="md"
triggerProps={{prefix: t('Agg')}}
options={
selectedMeta?.operations.filter(isAllowedOp).map(op => ({
label: op,
value: op,
})) ?? []
}
triggerLabel={metricsQuery.op}
disabled={!selectedMeta}
value={metricsQuery.op}
onChange={handleOpChange}
/>
</GuideAnchor>
<GuideAnchor target="metrics_groupby" position="bottom" disabled={index !== 0}>
<CompactSelect
multiple
size="md"
triggerProps={{prefix: t('Group by')}}
options={groupByOptions.map(tag => ({
label: tag.key,
value: tag.key,
trailingItems: tag.trailingItems ?? (
<Fragment>
{tag.key === 'release' && <IconReleases size="xs" />}
{tag.key === 'transaction' && <IconLightning size="xs" />}
</Fragment>
),
}))}
disabled={!metricsQuery.mri || tagsIsLoading}
value={metricsQuery.groupBy}
onChange={handleGroupByChange}
/>
</GuideAnchor>
</FlexBlock>
</FlexBlock>
<SearchBarWrapper>
Expand Down
23 changes: 13 additions & 10 deletions static/app/views/metrics/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import moment from 'moment';

import {updateDateTime} from 'sentry/actionCreators/pageFilters';
import Alert from 'sentry/components/alert';
import GuideAnchor from 'sentry/components/assistant/guideAnchor';
import TransparentLoadingMask from 'sentry/components/charts/transparentLoadingMask';
import type {DateTimeObject} from 'sentry/components/charts/utils';
import type {SelectOption} from 'sentry/components/compactSelect';
Expand Down Expand Up @@ -547,16 +548,18 @@ const MetricWidgetBody = memo(
</LimitAlert>
)}
<TransparentLoadingMask visible={isLoading} />
<MetricChart
ref={chartRef}
series={chartSeries}
displayType={displayType}
height={chartHeight}
samples={samplesProp}
focusArea={focusArea}
releases={releasesProp}
group={chartGroup}
/>
<GuideAnchor target="metrics_chart" disabled={widgetIndex !== 0}>
<MetricChart
ref={chartRef}
series={chartSeries}
displayType={displayType}
height={chartHeight}
samples={samplesProp}
focusArea={focusArea}
releases={releasesProp}
group={chartGroup}
/>
</GuideAnchor>
<SummaryTable
series={chartSeries}
onSortChange={handleSortChange}
Expand Down
7 changes: 6 additions & 1 deletion static/app/views/metrics/widgetDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Fragment, useCallback, useMemo, useState} from 'react';
import styled from '@emotion/styled';

import Feature from 'sentry/components/acl/feature';
import GuideAnchor from 'sentry/components/assistant/guideAnchor';
import {Button} from 'sentry/components/button';
import HookOrDefault from 'sentry/components/hookOrDefault';
import {
Expand Down Expand Up @@ -164,7 +165,11 @@ export function MetricDetails({
<Tabs value={selectedTab} onChange={handleTabChange}>
<TabsAndAction>
<TabList>
<TabList.Item key={Tab.SAMPLES}>{t('Sampled Events')}</TabList.Item>
<TabList.Item key={Tab.SAMPLES}>
<GuideAnchor target="metrics_table" position="top">
{t('Span Samples')}
</GuideAnchor>
</TabList.Item>
<TabList.Item
textValue={t('Code Location')}
key={Tab.CODE_LOCATIONS}
Expand Down

0 comments on commit f89be43

Please sign in to comment.