import {useMemo} from 'react';
import type {InjectedRouter} from 'react-router';
import styled from '@emotion/styled';
import {ErrorBoundary} from '@sentry/react';
import type {Location} from 'history';
import ErrorPanel from 'sentry/components/charts/errorPanel';
import {HeaderTitle} from 'sentry/components/charts/styles';
import TextOverflow from 'sentry/components/textOverflow';
import {IconWarning} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {PageFilters} from 'sentry/types/core';
import type {Organization} from 'sentry/types/organization';
import {parseMRI} from 'sentry/utils/metrics/mri';
import {MetricExpressionType} from 'sentry/utils/metrics/types';
import {useMetricsQuery} from 'sentry/utils/metrics/useMetricsQuery';
import {useVirtualMetricsContext} from 'sentry/utils/metrics/virtualMetricsContext';
import {MetricBigNumberContainer} from 'sentry/views/dashboards/metrics/bigNumber';
import {MetricChartContainer} from 'sentry/views/dashboards/metrics/chart';
import {MetricTableContainer} from 'sentry/views/dashboards/metrics/table';
import {
expressionsToApiQueries,
getMetricExpressions,
toMetricDisplayType,
} from 'sentry/views/dashboards/metrics/utils';
import type {DashboardFilters, Widget} from 'sentry/views/dashboards/types';
import {DisplayType} from 'sentry/views/dashboards/types';
import {WidgetCardPanel, WidgetTitleRow} from 'sentry/views/dashboards/widgetCard';
import {DashboardsMEPContext} from 'sentry/views/dashboards/widgetCard/dashboardsMEPContext';
import {Toolbar} from 'sentry/views/dashboards/widgetCard/toolbar';
import WidgetCardContextMenu from 'sentry/views/dashboards/widgetCard/widgetCardContextMenu';
import {useMetricsIntervalOptions} from 'sentry/views/metrics/utils/useMetricsIntervalParam';
import {getWidgetTitle} from 'sentry/views/metrics/widget';
type Props = {
isEditingDashboard: boolean;
location: Location;
organization: Organization;
router: InjectedRouter;
selection: PageFilters;
widget: Widget;
dashboardFilters?: DashboardFilters;
index?: string;
isMobile?: boolean;
onDelete?: () => void;
onDuplicate?: () => void;
onEdit?: (index: string) => void;
renderErrorMessage?: (errorMessage?: string) => React.ReactNode;
showContextMenu?: boolean;
};
const EMPTY_FN = () => {};
export function MetricWidgetCard({
organization,
selection,
widget,
isEditingDashboard,
onDelete,
onDuplicate,
location,
router,
dashboardFilters,
renderErrorMessage,
showContextMenu = true,
}: Props) {
const {getVirtualMRIQuery, isLoading: isLoadingVirtualMetrics} =
useVirtualMetricsContext();
const metricQueries = useMemo(
() =>
expressionsToApiQueries(
getMetricExpressions(widget, dashboardFilters, getVirtualMRIQuery)
),
[widget, dashboardFilters, getVirtualMRIQuery]
);
const hasSetMetric = useMemo(
() =>
getMetricExpressions(widget, dashboardFilters, getVirtualMRIQuery).some(
expression =>
expression.type === MetricExpressionType.QUERY &&
parseMRI(expression.mri)!.type === 's'
),
[widget, dashboardFilters, getVirtualMRIQuery]
);
const widgetMQL = useMemo(
() => (isLoadingVirtualMetrics ? '' : getWidgetTitle(metricQueries)),
[isLoadingVirtualMetrics, metricQueries]
);
const {interval: validatedInterval} = useMetricsIntervalOptions({
// TODO: Figure out why this can be undefined
interval: widget.interval ?? '',
hasSetMetric,
datetime: selection.datetime,
onIntervalChange: EMPTY_FN,
});
const {
data: timeseriesData,
isLoading,
isError,
error,
} = useMetricsQuery(metricQueries, selection, {
interval: validatedInterval,
});
const vizualizationComponent = useMemo(() => {
if (widget.displayType === DisplayType.TABLE) {
return (
);
}
if (widget.displayType === DisplayType.BIG_NUMBER) {
return (
);
}
return (
);
}, [widget.displayType, metricQueries, timeseriesData, isLoading, showContextMenu]);
return (
{},
}}
>
{widget.title || widgetMQL}
{showContextMenu && !isEditingDashboard && (
{
router.push({
pathname: `${location.pathname}${
location.pathname.endsWith('/') ? '' : '/'
}widget/${widget.id}/`,
query: location.query,
});
}}
router={router}
location={location}
onDelete={onDelete}
onDuplicate={onDuplicate}
/>
)}
{vizualizationComponent}
{isEditingDashboard && }
);
}
function WidgetCardBody({children, isError, timeseriesData, renderErrorMessage, error}) {
if (isError && !timeseriesData) {
const errorMessage =
error?.responseJSON?.detail?.toString() || t('Error while fetching metrics data');
return (
{renderErrorMessage?.(errorMessage)}
);
}
return children;
}
const WidgetHeaderWrapper = styled('div')`
min-height: 36px;
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
`;
const ContextMenuWrapper = styled('div')`
padding: ${space(2)} ${space(1)} 0 ${space(3)};
`;
const WidgetHeaderDescription = styled('div')`
${p => p.theme.overflowEllipsis};
overflow-y: visible;
`;
const WidgetTitle = styled(HeaderTitle)`
padding-left: ${space(3)};
padding-top: ${space(2)};
padding-right: ${space(1)};
${p => p.theme.overflowEllipsis};
font-weight: ${p => p.theme.fontWeightNormal};
`;
const ErrorWrapper = styled('div')`
padding-top: ${space(1)};
`;