From 8505e057fedd27e625bbf2a600fa752f5075c981 Mon Sep 17 00:00:00 2001 From: Dominik Buszowiecki <44422760+DominikB2014@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:49:52 -0400 Subject: [PATCH] feat(insights): add geo region selector in web vitals landing (#76062) Work towards #75230 Adds geo region selector, for now marked as "experimental" and internal only because we need to allow data to come in so its not always working. image This will go in many other modules and areas of our app, but for now just web vitals landing for testing purposes. --------- Co-authored-by: Ash <0Calories@users.noreply.github.com> --- .../webVitals/views/webVitalsLandingPage.tsx | 10 ++- .../spans/selectors/subregionSelector.tsx | 88 +++++++++++++++++++ static/app/views/insights/types.tsx | 33 ++++++- 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx diff --git a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx index b4d81acf54c54..b27ee75125374 100644 --- a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx +++ b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {Fragment, useState} from 'react'; import styled from '@emotion/styled'; import omit from 'lodash/omit'; @@ -34,6 +34,7 @@ import {ModulePageFilterBar} from 'sentry/views/insights/common/components/modul import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding'; import {useModuleBreadcrumbs} from 'sentry/views/insights/common/utils/useModuleBreadcrumbs'; +import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector'; import {ModuleName, SpanIndexedField} from 'sentry/views/insights/types'; export function WebVitalsLandingPage() { @@ -84,7 +85,12 @@ export function WebVitalsLandingPage() { } + extraFilters={ + + + + + } /> diff --git a/static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx b/static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx new file mode 100644 index 0000000000000..ad8a108d729ca --- /dev/null +++ b/static/app/views/insights/common/views/spans/selectors/subregionSelector.tsx @@ -0,0 +1,88 @@ +import {Fragment} from 'react'; +import styled from '@emotion/styled'; + +import FeatureBadge from 'sentry/components/badge/featureBadge'; +import { + CompactSelect, + type SelectOption, + type SelectProps, +} from 'sentry/components/compactSelect'; +import {t} from 'sentry/locale'; +import {space} from 'sentry/styles/space'; +import {trackAnalytics} from 'sentry/utils/analytics'; +import {decodeList} from 'sentry/utils/queryString'; +import {useLocation} from 'sentry/utils/useLocation'; +import {useNavigate} from 'sentry/utils/useNavigate'; +import useOrganization from 'sentry/utils/useOrganization'; +import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover'; +import {SpanMetricsField, subregionCodeToName} from 'sentry/views/insights/types'; + +export default function SubregionSelector() { + const organization = useOrganization(); + const location = useLocation(); + const navigate = useNavigate(); + const hasGeoSelectorFeature = organization.features.includes('insights-region-filter'); + + const value = decodeList(location.query[SpanMetricsField.USER_GEO_SUBREGION]); + const {data, isLoading} = useSpanMetrics( + {fields: [SpanMetricsField.USER_GEO_SUBREGION], enabled: hasGeoSelectorFeature}, + 'api.insights.user-geo-subregion-selector' + ); + + type Options = SelectProps['options']; + + const options: Options = + data?.map(row => { + const subregionCode = row[SpanMetricsField.USER_GEO_SUBREGION]; + const text = subregionCodeToName[subregionCode] || ''; + return { + value: subregionCode, + label: text, + textValue: text, + }; + }) ?? []; + + if (!hasGeoSelectorFeature) { + return ; + } + + return ( + + + {t('Geo region')} + + ), + }} + multiple + loading={isLoading} + clearable + value={value} + triggerLabel={value.length === 0 ? t('All') : undefined} + menuTitle={t('Filter region')} + options={options} + onChange={(selectedOptions: SelectOption[]) => { + trackAnalytics('insight.vital.select_browser_value', { + organization, + browsers: selectedOptions.map(v => v.value), + }); + + navigate({ + ...location, + query: { + ...location.query, + [SpanMetricsField.USER_GEO_SUBREGION]: selectedOptions.map( + option => option.value + ), + }, + }); + }} + /> + ); +} + +const StyledFeatureBadge = styled(FeatureBadge)` + margin-right: ${space(1)}; +`; diff --git a/static/app/views/insights/types.tsx b/static/app/views/insights/types.tsx index a57c84e4fafdd..1b0505e3f457c 100644 --- a/static/app/views/insights/types.tsx +++ b/static/app/views/insights/types.tsx @@ -56,6 +56,7 @@ export enum SpanMetricsField { URL_FULL = 'url.full', USER_AGENT_ORIGINAL = 'user_agent.original', CLIENT_ADDRESS = 'client.address', + USER_GEO_SUBREGION = 'user.geo.subregion', } export type SpanNumberFields = @@ -83,7 +84,8 @@ export type SpanStringFields = | 'span.status_code' | 'span.ai.pipeline.group' | 'project' - | 'messaging.destination.name'; + | 'messaging.destination.name' + | SpanMetricsField.USER_GEO_SUBREGION; export type SpanMetricsQueryFilters = { [Field in SpanStringFields]?: string; @@ -220,6 +222,7 @@ export enum SpanIndexedField { MESSAGING_MESSAGE_RECEIVE_LATENCY = 'measurements.messaging.message.receive.latency', MESSAGING_MESSAGE_RETRY_COUNT = 'measurements.messaging.message.retry.count', MESSAGING_MESSAGE_DESTINATION_NAME = 'messaging.destination.name', + USER_GEO_SUBREGION = 'user.geo.subregion', } export type SpanIndexedResponse = { @@ -287,6 +290,7 @@ export type SpanIndexedResponse = { [SpanIndexedField.MESSAGING_MESSAGE_RECEIVE_LATENCY]: number; [SpanIndexedField.MESSAGING_MESSAGE_RETRY_COUNT]: number; [SpanIndexedField.MESSAGING_MESSAGE_DESTINATION_NAME]: string; + [SpanIndexedField.USER_GEO_SUBREGION]: string; }; export type SpanIndexedPropery = keyof SpanIndexedResponse; @@ -336,3 +340,30 @@ export type MetricsQueryFilters = { } & { [SpanIndexedField.PROJECT_ID]?: string; }; + +// Maps the subregion code to the subregion name according to UN m49 standard +// We also define this in relay in `country_subregion.rs` +export const subregionCodeToName = { + '21': 'North America', + '13': 'Central America', + '29': 'Caribbean', + '5': 'South America', + '154': 'Northern Europe', + '155': 'Western Europe', + '39': 'Southern Europe', + '151': 'Eastern Europe', + '30': 'Eastern Asia', + '34': 'Southern Asia', + '35': 'South Eastern Asia', + '145': 'Western Asia', + '143': 'Central Asia', + '15': 'Northern Africa', + '11': 'Western Africa', + '17': 'Middle Africa', + '14': 'Eastern Africa', + '18': 'Southern Africa', + '54': 'Melanesia', + '57': 'Micronesia', + '61': 'Polynesia', + '53': 'Australia and New Zealand', +};