import {useMemo} from 'react'; import styled from '@emotion/styled'; import Alert from 'sentry/components/alert'; import {Button} from 'sentry/components/button'; import {IconClose, IconInfo} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Project, ProjectSdkUpdates} from 'sentry/types'; import {decodeScalar} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useDismissAlert from 'sentry/utils/useDismissAlert'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import useProjects from 'sentry/utils/useProjects'; import {useProjectSdkUpdates} from 'sentry/utils/useProjectSdkUpdates'; import {semverCompare} from 'sentry/utils/versions'; const MIN_REPLAY_CLICK_SDK = '7.44.0'; const LOCAL_STORAGE_KEY = 'replay-search-min-sdk-alert-dismissed'; // exported for testing export function ReplaySearchAlert() { const {selection} = usePageFilters(); const projects = useProjects(); const location = useLocation(); const organization = useOrganization(); const sdkUpdates = useProjectSdkUpdates({ organization, projectId: null, }); const {dismiss: handleDismiss, isDismissed} = useDismissAlert({ key: LOCAL_STORAGE_KEY, }); const conditions = useMemo(() => { return new MutableSearch(decodeScalar(location.query.query, '')); }, [location.query.query]); const hasReplayClick = conditions.getFilterKeys().some(k => k.startsWith('click.')); if (sdkUpdates.type !== 'resolved') { return null; } const selectedProjectsWithSdkUpdates = sdkUpdates.data?.reduce((acc, sdkUpdate) => { if (!selection.projects.includes(Number(sdkUpdate.projectId))) { return acc; } const project = projects.projects.find(p => p.id === sdkUpdate.projectId); // should never really happen but making ts happy if (!project) { return acc; } acc.push({ project, sdkUpdate, }); return acc; }, [] as Array<{project: Project; sdkUpdate: ProjectSdkUpdates}>); const doesNotMeetMinSDK = selectedProjectsWithSdkUpdates && selectedProjectsWithSdkUpdates.length > 0 && selectedProjectsWithSdkUpdates.every(({sdkUpdate}) => { return semverCompare(sdkUpdate.sdkVersion, MIN_REPLAY_CLICK_SDK) === -1; }); if (!doesNotMeetMinSDK) { return null; } if (hasReplayClick) { return ( {tct( 'Search field [click] requires a minimum SDK version of >= [minSdkVersion].', { click: 'click', minSdkVersion: {MIN_REPLAY_CLICK_SDK}, } )} ); } if (isDismissed) { return null; } return ( {tct( 'Search for dom elements clicked during a replay by using our new search key [click]. Sadly, it requires an SDK version >= [version]', { click: {`'click'`}, version: {MIN_REPLAY_CLICK_SDK}, } )}