123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- import styled from '@emotion/styled';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconFile} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import getDuration from 'sentry/utils/duration/getDuration';
- import {useResourcesQuery} from 'sentry/views/insights/browser/common/queries/useResourcesQuery';
- import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types';
- import {SpanMetricsField} from 'sentry/views/insights/types';
- export function Recommendations({
- transaction,
- webVital,
- }: {
- transaction: string;
- webVital: WebVitals;
- }) {
- switch (webVital) {
- case 'lcp':
- return null;
- case 'cls':
- return null;
- case 'fcp':
- return <FcpRecommendations transaction={transaction} />;
- case 'ttfb':
- return null;
- default:
- return null;
- }
- }
- function FcpRecommendations({transaction}: {transaction: string}) {
- const query = `transaction:"${transaction}" resource.render_blocking_status:blocking`;
- const {data, isPending} = useResourcesQuery({
- query,
- sort: {field: `avg(${SpanMetricsField.SPAN_SELF_TIME})`, kind: 'desc'},
- defaultResourceTypes: ['resource.script', 'resource.css', 'resource.img'],
- limit: 7,
- referrer: 'api.performance.browser.web-vitals.fcp-recommendations',
- });
- if (isPending || !data || data.length < 1) {
- return null;
- }
- return (
- <RecommendationsContainer>
- <RecommendationsHeader />
- <ul>
- <RecommendationSubHeader>
- {t('Eliminate render blocking resources')}
- </RecommendationSubHeader>
- <ResourceList>
- {data.map(
- ({
- 'span.op': op,
- 'span.description': description,
- 'avg(span.self_time)': duration,
- }) => {
- return (
- <ResourceListItem key={description}>
- <Flex>
- <ResourceDescription>
- <StyledTooltip title={description}>
- <ResourceType resourceType={op} />
- {description}
- </StyledTooltip>
- </ResourceDescription>
- <span>{getFormattedDuration(duration)}</span>
- </Flex>
- </ResourceListItem>
- );
- }
- )}
- </ResourceList>
- </ul>
- </RecommendationsContainer>
- );
- }
- function RecommendationsHeader() {
- return (
- <RecommendationsHeaderContainer>
- <b>{t('Recommendations')}</b>
- </RecommendationsHeaderContainer>
- );
- }
- function ResourceType({resourceType}: {resourceType: `resource.${string}`}) {
- switch (resourceType) {
- case 'resource.script':
- return (
- <b>
- <StyledIconFile size="xs" />
- {t('js')}
- {' \u2014 '}
- </b>
- );
- case 'resource.css':
- return (
- <b>
- <StyledIconFile size="xs" />
- {t('css')}
- {' \u2014 '}
- </b>
- );
- case 'resource.img':
- return (
- <b>
- <StyledIconFile size="xs" />
- {t('img')}
- {' \u2014 '}
- </b>
- );
- default:
- return null;
- }
- }
- const getFormattedDuration = (value: number | null) => {
- if (value === null) {
- return null;
- }
- if (value < 1000) {
- return getDuration(value / 1000, 0, true);
- }
- return getDuration(value / 1000, 2, true);
- };
- const StyledIconFile = styled(IconFile)`
- margin-right: ${space(0.5)};
- `;
- const RecommendationSubHeader = styled('li')`
- margin-bottom: ${space(1)};
- `;
- const RecommendationsHeaderContainer = styled('div')`
- margin-bottom: ${space(1)};
- font-size: ${p => p.theme.fontSizeExtraLarge};
- `;
- const ResourceList = styled('ul')`
- padding-left: ${space(1)};
- `;
- const ResourceListItem = styled('li')`
- margin-bottom: ${space(0.5)};
- list-style: none;
- white-space: nowrap;
- `;
- const ResourceDescription = styled('span')`
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- `;
- const Flex = styled('span')`
- display: flex;
- justify-content: space-between;
- gap: ${space(1)};
- `;
- const RecommendationsContainer = styled('div')`
- margin-bottom: ${space(4)};
- `;
- const StyledTooltip = styled(Tooltip)`
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- `;
|