123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import {Fragment, useCallback, useMemo, useState} from 'react';
- import {css} from '@emotion/react';
- import type {ModalRenderProps} from 'sentry/actionCreators/modal';
- import {Button, LinkButton} from 'sentry/components/button';
- import ButtonBar from 'sentry/components/buttonBar';
- import {
- MetricWidgetTitle,
- type MetricWidgetTitleState,
- } from 'sentry/components/modals/metricWidgetViewerModal/header';
- import {Queries} from 'sentry/components/modals/metricWidgetViewerModal/queries';
- import {MetricVisualization} from 'sentry/components/modals/metricWidgetViewerModal/visualization';
- import type {WidgetViewerModalOptions} from 'sentry/components/modals/widgetViewerModal';
- import {t} from 'sentry/locale';
- import type {Organization} from 'sentry/types';
- import {getDdmUrl} from 'sentry/utils/metrics';
- import {toDisplayType} from 'sentry/utils/metrics/dashboard';
- import {MetricQueryType} from 'sentry/utils/metrics/types';
- import usePageFilters from 'sentry/utils/usePageFilters';
- import type {
- DashboardMetricsEquation,
- DashboardMetricsQuery,
- Order,
- } from 'sentry/views/dashboards/metrics/types';
- import {
- expressionsToApiQueries,
- expressionsToWidget,
- filterEquationsByDisplayType,
- filterQueriesByDisplayType,
- getMetricEquations,
- getMetricQueries,
- getMetricWidgetTitle,
- useGenerateExpressionId,
- } from 'sentry/views/dashboards/metrics/utils';
- import {MetricDetails} from 'sentry/views/ddm/widgetDetails';
- import {OrganizationContext} from 'sentry/views/organizationContext';
- interface Props extends ModalRenderProps, WidgetViewerModalOptions {
- organization: Organization;
- }
- function MetricWidgetViewerModal({
- organization,
- widget,
- Footer,
- Body,
- Header,
- closeModal,
- onMetricWidgetEdit,
- dashboardFilters,
- }: Props) {
- const {selection} = usePageFilters();
- const [displayType, setDisplayType] = useState(widget.displayType);
- const [metricQueries, setMetricQueries] = useState<DashboardMetricsQuery[]>(() =>
- getMetricQueries(widget, dashboardFilters)
- );
- const [metricEquations, setMetricEquations] = useState<DashboardMetricsEquation[]>(() =>
- getMetricEquations(widget)
- );
- const filteredQueries = useMemo(
- () => filterQueriesByDisplayType(metricQueries, displayType),
- [metricQueries, displayType]
- );
- const filteredEquations = useMemo(
- () =>
- filterEquationsByDisplayType(metricEquations, displayType).filter(
- equation => equation.formula !== ''
- ),
- [metricEquations, displayType]
- );
- const generateQueryId = useGenerateExpressionId(metricQueries);
- const generateEquationId = useGenerateExpressionId(metricEquations);
- const apiQueries = useMemo(
- () => expressionsToApiQueries([...filteredQueries, ...filteredEquations]),
- [filteredQueries, filteredEquations]
- );
- const widgetMQL = useMemo(
- () => getMetricWidgetTitle([...filteredQueries, ...filteredEquations]),
- [filteredQueries, filteredEquations]
- );
- const [title, setTitle] = useState<MetricWidgetTitleState>({
- stored: widget.title,
- edited: widget.title,
- isEditing: false,
- });
- const handleTitleChange = useCallback(
- (patch: Partial<MetricWidgetTitleState>) => {
- setTitle(curr => ({...curr, ...patch}));
- },
- [setTitle]
- );
- const handleQueryChange = useCallback(
- (data: Partial<DashboardMetricsQuery>, index: number) => {
- setMetricQueries(curr => {
- const updated = [...curr];
- updated[index] = {...updated[index], ...data} as DashboardMetricsQuery;
- return updated;
- });
- },
- [setMetricQueries]
- );
- const handleEquationChange = useCallback(
- (data: Partial<DashboardMetricsEquation>, index: number) => {
- setMetricEquations(curr => {
- const updated = [...curr];
- updated[index] = {...updated[index], ...data} as DashboardMetricsEquation;
- return updated;
- });
- },
- [setMetricEquations]
- );
- const handleOrderChange = useCallback(
- ({id, order}: {id: number; order: Order}) => {
- const queryIdx = filteredQueries.findIndex(query => query.id === id);
- if (queryIdx > -1) {
- setMetricQueries(curr => {
- return curr.map((query, i) => {
- const orderBy = i === queryIdx ? order : undefined;
- return {...query, orderBy};
- });
- });
- return;
- }
- const equationIdx = filteredEquations.findIndex(equation => equation.id === id);
- if (equationIdx > -1) {
- setMetricEquations(curr => {
- return curr.map((equation, i) => {
- const orderBy = i === equationIdx ? order : undefined;
- return {...equation, orderBy};
- });
- });
- }
- },
- [filteredEquations, filteredQueries]
- );
- const addQuery = useCallback(() => {
- setMetricQueries(curr => {
- return [
- ...curr,
- {
- ...metricQueries[metricQueries.length - 1],
- id: generateQueryId(),
- },
- ];
- });
- }, [generateQueryId, metricQueries]);
- const addEquation = useCallback(() => {
- setMetricEquations(curr => {
- return [
- ...curr,
- {
- formula: '',
- name: '',
- id: generateEquationId(),
- type: MetricQueryType.FORMULA,
- },
- ];
- });
- }, [generateEquationId]);
- const removeEquation = useCallback(
- (index: number) => {
- setMetricEquations(curr => {
- const updated = [...curr];
- updated.splice(index, 1);
- return updated;
- });
- },
- [setMetricEquations]
- );
- const removeQuery = useCallback(
- (index: number) => {
- setMetricQueries(curr => {
- const updated = [...curr];
- updated.splice(index, 1);
- return updated;
- });
- },
- [setMetricQueries]
- );
- const handleSubmit = useCallback(() => {
- const convertedWidget = expressionsToWidget(
- [...filteredQueries, ...filteredEquations],
- title.edited,
- toDisplayType(displayType)
- );
- onMetricWidgetEdit?.(convertedWidget);
- closeModal();
- }, [
- filteredQueries,
- filteredEquations,
- title.edited,
- displayType,
- onMetricWidgetEdit,
- closeModal,
- ]);
- return (
- <Fragment>
- <OrganizationContext.Provider value={organization}>
- <Header closeButton>
- <MetricWidgetTitle
- title={title}
- onTitleChange={handleTitleChange}
- placeholder={widgetMQL}
- description={widget.description}
- />
- </Header>
- <Body>
- <Queries
- displayType={displayType}
- metricQueries={metricQueries}
- metricEquations={metricEquations}
- onQueryChange={handleQueryChange}
- onEquationChange={handleEquationChange}
- addEquation={addEquation}
- addQuery={addQuery}
- removeEquation={removeEquation}
- removeQuery={removeQuery}
- />
- <MetricVisualization
- queries={apiQueries}
- displayType={displayType}
- onDisplayTypeChange={setDisplayType}
- onOrderChange={handleOrderChange}
- />
- <MetricDetails mri={metricQueries[0].mri} query={metricQueries[0].query} />
- </Body>
- <Footer>
- <ButtonBar gap={1}>
- <LinkButton
- to={getDdmUrl(organization.slug, {
- widgets: [...metricQueries, ...metricEquations],
- ...selection.datetime,
- project: selection.projects,
- environment: selection.environments,
- })}
- >
- {t('Open in Metrics')}
- </LinkButton>
- <Button onClick={closeModal}>{t('Close')}</Button>
- <Button priority="primary" onClick={handleSubmit}>
- {t('Save changes')}
- </Button>
- </ButtonBar>
- </Footer>
- </OrganizationContext.Provider>
- </Fragment>
- );
- }
- export default MetricWidgetViewerModal;
- export const modalCss = css`
- width: 100%;
- max-width: 1200px;
- `;
|