import React, {useMemo, useState} from 'react';
import styled from '@emotion/styled';
import omit from 'lodash/omit';
import moment from 'moment';
import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
import {Breadcrumbs} from 'sentry/components/breadcrumbs';
import {LinkButton} from 'sentry/components/button';
import ButtonBar from 'sentry/components/buttonBar';
import {AggregateSpans} from 'sentry/components/events/interfaces/spans/aggregateSpans';
import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
import * as Layout from 'sentry/components/layouts/thirds';
import ExternalLink from 'sentry/components/links/externalLink';
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
import {TabList, Tabs} from 'sentry/components/tabs';
import {IconChevron, IconClose} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import ConfigStore from 'sentry/stores/configStore';
import {space} from 'sentry/styles/space';
import {defined} from 'sentry/utils';
import {browserHistory} from 'sentry/utils/browserHistory';
import {decodeScalar} from 'sentry/utils/queryString';
import useDismissAlert from 'sentry/utils/useDismissAlert';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import useRouter from 'sentry/utils/useRouter';
import {PageOverviewSidebar} from 'sentry/views/performance/browser/webVitals/components/pageOverviewSidebar';
import {
FID_DEPRECATION_DATE,
PerformanceScoreBreakdownChart,
} from 'sentry/views/performance/browser/webVitals/components/performanceScoreBreakdownChart';
import WebVitalMeters from 'sentry/views/performance/browser/webVitals/components/webVitalMeters';
import {PageOverviewWebVitalsDetailPanel} from 'sentry/views/performance/browser/webVitals/pageOverviewWebVitalsDetailPanel';
import {PageSamplePerformanceTable} from 'sentry/views/performance/browser/webVitals/pageSamplePerformanceTable';
import {useProjectRawWebVitalsQuery} from 'sentry/views/performance/browser/webVitals/utils/queries/rawWebVitalsQueries/useProjectRawWebVitalsQuery';
import {calculatePerformanceScoreFromStoredTableDataRow} from 'sentry/views/performance/browser/webVitals/utils/queries/storedScoreQueries/calculatePerformanceScoreFromStored';
import {useProjectWebVitalsScoresQuery} from 'sentry/views/performance/browser/webVitals/utils/queries/storedScoreQueries/useProjectWebVitalsScoresQuery';
import type {WebVitals} from 'sentry/views/performance/browser/webVitals/utils/types';
import {
AlertContent,
DismissButton,
StyledAlert,
} from 'sentry/views/performance/browser/webVitals/webVitalsLandingPage';
import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders';
import {useModuleBreadcrumbs} from 'sentry/views/performance/utils/useModuleBreadcrumbs';
import {useModuleURL} from 'sentry/views/performance/utils/useModuleURL';
import {transactionSummaryRouteWithQuery} from '../../transactionSummary/utils';
export enum LandingDisplayField {
OVERVIEW = 'overview',
SPANS = 'spans',
}
const LANDING_DISPLAYS = [
{
label: t('Overview'),
field: LandingDisplayField.OVERVIEW,
},
{
label: t('Aggregate Spans'),
field: LandingDisplayField.SPANS,
},
];
function getCurrentTabSelection(selectedTab) {
const tab = decodeScalar(selectedTab);
if (tab && Object.values(LandingDisplayField).includes(tab as LandingDisplayField)) {
return tab as LandingDisplayField;
}
return LandingDisplayField.OVERVIEW;
}
export function PageOverview() {
const moduleURL = useModuleURL('vital');
const organization = useOrganization();
const location = useLocation();
const {projects} = useProjects();
const router = useRouter();
const transaction = location.query.transaction
? Array.isArray(location.query.transaction)
? location.query.transaction[0]
: location.query.transaction
: undefined;
const project = useMemo(
() => projects.find(p => p.id === String(location.query.project)),
[projects, location.query.project]
);
const tab = getCurrentTabSelection(location.query.tab);
// TODO: When visiting page overview from a specific webvital detail panel in the landing page,
// we should automatically default this webvital state to the respective webvital so the detail
// panel in this page opens automatically.
const [state, setState] = useState<{webVital: WebVitals | null}>({
webVital: (location.query.webVital as WebVitals) ?? null,
});
const user = ConfigStore.get('user');
const {dismiss, isDismissed} = useDismissAlert({
key: `${organization.slug}-${user.id}:fid-deprecation-message-dismissed`,
});
const crumbs = useModuleBreadcrumbs('vital');
const query = decodeScalar(location.query.query);
const {data: pageData, isLoading} = useProjectRawWebVitalsQuery({transaction});
const {data: projectScores, isLoading: isProjectScoresLoading} =
useProjectWebVitalsScoresQuery({transaction});
if (transaction === undefined) {
// redirect user to webvitals landing page
window.location.href = moduleURL;
return null;
}
const transactionSummaryTarget =
project &&
!Array.isArray(location.query.project) && // Only render button to transaction summary when one project is selected.
transaction &&
transactionSummaryRouteWithQuery({
orgSlug: organization.slug,
transaction,
query: {...location.query},
projectID: project.id,
});
const projectScore =
isProjectScoresLoading || isLoading
? undefined
: calculatePerformanceScoreFromStoredTableDataRow(projectScores?.data?.[0]);
const fidDeprecationTimestampString =
moment(FID_DEPRECATION_DATE).format('DD MMMM YYYY');
return (
{
browserHistory.push({
...location,
query: {
...location.query,
tab: value,
},
});
}}
>
{transaction && project && }
{transaction ?? t('Page Loads')}
{transactionSummaryTarget && (
{t('View Transaction Summary')}
)}
{LANDING_DISPLAYS.map(({label, field}) => (
{label}
))}
{tab === LandingDisplayField.SPANS ? (
{defined(transaction) && }
) : (
{transaction && (
{t('View All Pages')}
)}
{!isDismissed && (
{tct(
`Starting on [fidDeprecationTimestampString], [inpStrong:INP] (Interaction to Next Paint) will replace [fidStrong:FID] (First Input Delay) in our performance score calculation.`,
{
fidDeprecationTimestampString,
inpStrong: ,
fidStrong: ,
}
)}
{tct(
`Users should update their Sentry SDKs to the [link:latest version (7.104.0+)] and [enableInp:enable the INP option] to start receiving updated Performance Scores.`,
{
link: (
),
enableInp: (
),
}
)}
}
onClick={dismiss}
aria-label={t('Dismiss Alert')}
title={t('Dismiss Alert')}
/>
)}
{
router.replace({
pathname: location.pathname,
query: {...location.query, webVital},
});
setState({...state, webVital});
}}
transaction={transaction}
showTooltip={false}
/>
)}
{
router.replace({
pathname: router.location.pathname,
query: omit(router.location.query, 'webVital'),
});
setState({...state, webVital: null});
}}
/>
);
}
function PageWithProviders() {
return (
);
}
export default PageWithProviders;
const ViewAllPagesButton = styled(LinkButton)`
margin-right: ${space(1)};
`;
const TopMenuContainer = styled('div')`
margin-bottom: ${space(1)};
display: flex;
`;
const Flex = styled('div')`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
gap: ${space(1)};
`;
const PageSamplePerformanceTableContainer = styled('div')`
margin-top: ${space(1)};
`;
const WebVitalMetersContainer = styled('div')`
margin: ${space(2)} 0 ${space(4)} 0;
`;