123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- import styled from '@emotion/styled';
- import {Location} from 'history';
- import {SectionHeading} from 'sentry/components/charts/styles';
- import {DropdownMenu} from 'sentry/components/dropdownMenu';
- import Panel from 'sentry/components/panels/panel';
- import {IconEllipsis} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import {Organization} from 'sentry/types';
- import {Event} from 'sentry/types/event';
- import EventView from 'sentry/utils/discover/eventView';
- import {
- DURATION_UNITS,
- FIELD_FORMATTERS,
- PERCENTAGE_UNITS,
- SIZE_UNITS,
- } from 'sentry/utils/discover/fieldRenderers';
- import {isCustomMeasurement} from 'sentry/views/dashboards/utils';
- import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';
- export enum EventDetailPageSource {
- PERFORMANCE = 'performance',
- DISCOVER = 'discover',
- }
- type Props = {
- event: Event;
- location: Location;
- organization: Organization;
- isHomepage?: boolean;
- source?: EventDetailPageSource;
- };
- function isNotMarkMeasurement(field: string) {
- return !field.startsWith('mark.');
- }
- export default function EventCustomPerformanceMetrics({
- event,
- location,
- organization,
- source,
- isHomepage,
- }: Props) {
- const measurementNames = Object.keys(event.measurements ?? {})
- .filter(name => isCustomMeasurement(`measurements.${name}`))
- .filter(isNotMarkMeasurement)
- .sort();
- if (measurementNames.length === 0) {
- return null;
- }
- return (
- <Container>
- <SectionHeading>{t('Custom Performance Metrics')}</SectionHeading>
- <Measurements>
- {measurementNames.map(name => {
- return (
- <EventCustomPerformanceMetric
- key={name}
- event={event}
- name={name}
- location={location}
- organization={organization}
- source={source}
- isHomepage={isHomepage}
- />
- );
- })}
- </Measurements>
- </Container>
- );
- }
- type EventCustomPerformanceMetricProps = Props & {
- name: string;
- };
- export function getFieldTypeFromUnit(unit) {
- if (unit) {
- if (DURATION_UNITS[unit]) {
- return 'duration';
- }
- if (SIZE_UNITS[unit]) {
- return 'size';
- }
- if (PERCENTAGE_UNITS.includes(unit)) {
- return 'percentage';
- }
- if (unit === 'none') {
- return 'integer';
- }
- return 'string';
- }
- return 'number';
- }
- function EventCustomPerformanceMetric({
- event,
- name,
- location,
- organization,
- source,
- isHomepage,
- }: EventCustomPerformanceMetricProps) {
- const {value, unit} = event.measurements?.[name] ?? {};
- if (value === null) {
- return null;
- }
- const fieldType = getFieldTypeFromUnit(unit);
- const renderValue = fieldType === 'string' ? `${value} ${unit}` : value;
- const rendered = fieldType
- ? FIELD_FORMATTERS[fieldType].renderFunc(
- name,
- {[name]: renderValue},
- {location, organization, unit}
- )
- : renderValue;
- function generateLinkWithQuery(query: string) {
- const eventView = EventView.fromLocation(location);
- eventView.query = query;
- switch (source) {
- case EventDetailPageSource.PERFORMANCE:
- return transactionSummaryRouteWithQuery({
- orgSlug: organization.slug,
- transaction: event.title,
- projectID: event.projectID,
- query: {query},
- });
- case EventDetailPageSource.DISCOVER:
- default:
- return eventView.getResultsViewUrlTarget(organization.slug, isHomepage);
- }
- }
- // Some custom perf metrics have units.
- // These custom perf metrics need to be adjusted to the correct value.
- let customMetricValue = value;
- if (typeof value === 'number' && unit && customMetricValue) {
- if (Object.keys(SIZE_UNITS).includes(unit)) {
- customMetricValue *= SIZE_UNITS[unit];
- } else if (Object.keys(DURATION_UNITS).includes(unit)) {
- customMetricValue *= DURATION_UNITS[unit];
- }
- }
- return (
- <StyledPanel>
- <div>
- <div>{name}</div>
- <ValueRow>
- <Value>{rendered}</Value>
- </ValueRow>
- </div>
- <StyledDropdownMenuControl
- items={[
- {
- key: 'includeEvents',
- label: t('Show events with this value'),
- to: generateLinkWithQuery(`measurements.${name}:${customMetricValue}`),
- },
- {
- key: 'excludeEvents',
- label: t('Hide events with this value'),
- to: generateLinkWithQuery(`!measurements.${name}:${customMetricValue}`),
- },
- {
- key: 'includeGreaterThanEvents',
- label: t('Show events with values greater than'),
- to: generateLinkWithQuery(`measurements.${name}:>${customMetricValue}`),
- },
- {
- key: 'includeLessThanEvents',
- label: t('Show events with values less than'),
- to: generateLinkWithQuery(`measurements.${name}:<${customMetricValue}`),
- },
- ]}
- triggerProps={{
- 'aria-label': t('Widget actions'),
- size: 'xs',
- borderless: true,
- showChevron: false,
- icon: <IconEllipsis direction="down" size="sm" />,
- }}
- position="bottom-end"
- />
- </StyledPanel>
- );
- }
- const Measurements = styled('div')`
- display: grid;
- grid-column-gap: ${space(1)};
- `;
- const Container = styled('div')`
- font-size: ${p => p.theme.fontSizeMedium};
- margin-bottom: ${space(4)};
- `;
- const StyledPanel = styled(Panel)`
- padding: ${space(1)} ${space(1.5)};
- margin-bottom: ${space(1)};
- display: flex;
- `;
- const ValueRow = styled('div')`
- display: flex;
- align-items: center;
- `;
- const Value = styled('span')`
- font-size: ${p => p.theme.fontSizeExtraLarge};
- `;
- const StyledDropdownMenuControl = styled(DropdownMenu)`
- margin-left: auto;
- `;
|