import {Fragment, useCallback, useEffect, useState} from 'react';
import {browserHistory} from 'react-router';
import styled from '@emotion/styled';
import debounce from 'lodash/debounce';
import FeatureBadge from 'sentry/components/featureBadge';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import {RESOURCE_THROUGHPUT_UNIT} from 'sentry/views/performance/browser/resources';
import ResourceTable from 'sentry/views/performance/browser/resources/resourceView/resourceTable';
import {
FONT_FILE_EXTENSIONS,
IMAGE_FILE_EXTENSIONS,
} from 'sentry/views/performance/browser/resources/shared/constants';
import RenderBlockingSelector from 'sentry/views/performance/browser/resources/shared/renderBlockingSelector';
import SelectControlWithProps from 'sentry/views/performance/browser/resources/shared/selectControlWithProps';
import {ResourceSpanOps} from 'sentry/views/performance/browser/resources/shared/types';
import {
BrowserStarfishFields,
useResourceModuleFilters,
} from 'sentry/views/performance/browser/resources/utils/useResourceFilters';
import {useResourcePagesQuery} from 'sentry/views/performance/browser/resources/utils/useResourcePagesQuery';
import {useResourceSort} from 'sentry/views/performance/browser/resources/utils/useResourceSort';
import {getResourceTypeFilter} from 'sentry/views/performance/browser/resources/utils/useResourcesQuery';
import {ModuleName} from 'sentry/views/starfish/types';
import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters';
import {SpanTimeCharts} from 'sentry/views/starfish/views/spans/spanTimeCharts';
import type {ModuleFilters} from 'sentry/views/starfish/views/spans/useModuleFilters';
const {
SPAN_OP: RESOURCE_TYPE,
SPAN_DOMAIN,
TRANSACTION,
RESOURCE_RENDER_BLOCKING_STATUS,
} = BrowserStarfishFields;
export const DEFAULT_RESOURCE_TYPES = [
ResourceSpanOps.SCRIPT,
ResourceSpanOps.CSS,
ResourceSpanOps.FONT,
ResourceSpanOps.IMAGE,
];
type Option = {
label: string | React.ReactElement;
value: string;
};
function ResourceView() {
const filters = useResourceModuleFilters();
const sort = useResourceSort();
const spanTimeChartsFilters: ModuleFilters = {
'span.op': `[${DEFAULT_RESOURCE_TYPES.join(',')}]`,
...(filters[SPAN_DOMAIN] ? {[SPAN_DOMAIN]: filters[SPAN_DOMAIN]} : {}),
};
const extraQuery = getResourceTypeFilter(undefined, DEFAULT_RESOURCE_TYPES);
return (
);
}
function ResourceTypeSelector({value}: {value?: string}) {
const location = useLocation();
const {features} = useOrganization();
const hasImageView = features.includes('starfish-browser-resource-module-image-view');
const options: Option[] = [
{value: '', label: 'All'},
{value: 'resource.script', label: `${t('JavaScript')} (.js)`},
{value: 'resource.css', label: `${t('Stylesheet')} (.css)`},
{
value: 'resource.font',
label: `${t('Font')} (${FONT_FILE_EXTENSIONS.map(e => `.${e}`).join(', ')})`,
},
...(hasImageView
? [
{
value: ResourceSpanOps.IMAGE,
label: (
{`${t('Image')} (${IMAGE_FILE_EXTENSIONS.map(e => `.${e}`).join(', ')})`}
),
},
]
: []),
];
return (
{
browserHistory.push({
...location,
query: {
...location.query,
[RESOURCE_TYPE]: newValue?.value,
[QueryParameterNames.SPANS_CURSOR]: undefined,
},
});
}}
/>
);
}
export function TransactionSelector({
value,
defaultResourceTypes,
}: {
defaultResourceTypes?: string[];
value?: string;
}) {
const [state, setState] = useState({
search: '',
inputChanged: false,
shouldRequeryOnInputChange: false,
});
const location = useLocation();
const {data: pages, isLoading} = useResourcePagesQuery(
defaultResourceTypes,
state.search
);
// If the maximum number of pages is returned, we need to requery on input change to get full results
if (!state.shouldRequeryOnInputChange && pages && pages.length >= 100) {
setState({...state, shouldRequeryOnInputChange: true});
}
// Everytime loading is complete, reset the inputChanged state
useEffect(() => {
if (!isLoading && state.inputChanged) {
setState({...state, inputChanged: false});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading]);
const optionsReady = !isLoading && !state.inputChanged;
const options: Option[] = optionsReady
? [{value: '', label: 'All'}, ...pages.map(page => ({value: page, label: page}))]
: [];
// eslint-disable-next-line react-hooks/exhaustive-deps
const debounceUpdateSearch = useCallback(
debounce((search, currentState) => {
setState({...currentState, search});
}, 500),
[]
);
return (
{
if (state.shouldRequeryOnInputChange) {
setState({...state, inputChanged: true});
debounceUpdateSearch(input, state);
}
}}
noOptionsMessage={() => (optionsReady ? undefined : t('Loading...'))}
onChange={newValue => {
browserHistory.push({
...location,
query: {
...location.query,
[TRANSACTION]: newValue?.value,
[QueryParameterNames.SPANS_CURSOR]: undefined,
},
});
}}
/>
);
}
export const SpanTimeChartsContainer = styled('div')`
margin-bottom: ${space(2)};
`;
export const FilterOptionsContainer = styled('div')<{columnCount: number}>`
display: grid;
grid-template-columns: repeat(${props => props.columnCount}, 1fr);
gap: ${space(2)};
margin-bottom: ${space(2)};
max-width: 800px;
`;
export default ResourceView;