mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
167 lines
4.9 KiB
TypeScript
167 lines
4.9 KiB
TypeScript
import type {CSSProperties} from 'react';
|
|
import {useCallback, useEffect, useState} from 'react';
|
|
import {css} from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
import type {Location} from 'history';
|
|
import debounce from 'lodash/debounce';
|
|
import isEqual from 'lodash/isEqual';
|
|
|
|
import {TableCell} from 'sentry/components/charts/simpleTableChart';
|
|
import SelectControl from 'sentry/components/forms/controls/selectControl';
|
|
import FieldGroup from 'sentry/components/forms/fieldGroup';
|
|
import PanelAlert from 'sentry/components/panels/panelAlert';
|
|
import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
|
|
import {t} from 'sentry/locale';
|
|
import {space} from 'sentry/styles/space';
|
|
import type {Organization, PageFilters, SelectValue} from 'sentry/types';
|
|
import type {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
|
|
import usePrevious from 'sentry/utils/usePrevious';
|
|
import type {DashboardFilters, Widget} from 'sentry/views/dashboards/types';
|
|
import {DisplayType} from 'sentry/views/dashboards/types';
|
|
|
|
import {getDashboardFiltersFromURL} from '../../utils';
|
|
import WidgetCard, {WidgetCardPanel} from '../../widgetCard';
|
|
import {displayTypes} from '../utils';
|
|
|
|
import {BuildStep} from './buildStep';
|
|
|
|
interface Props {
|
|
displayType: DisplayType;
|
|
isWidgetInvalid: boolean;
|
|
location: Location;
|
|
onChange: (displayType: DisplayType) => void;
|
|
organization: Organization;
|
|
pageFilters: PageFilters;
|
|
widget: Widget;
|
|
dashboardFilters?: DashboardFilters;
|
|
error?: string;
|
|
noDashboardsMEPProvider?: boolean;
|
|
onDataFetched?: (results: TableDataWithTitle[]) => void;
|
|
}
|
|
|
|
export function VisualizationStep({
|
|
organization,
|
|
pageFilters,
|
|
displayType,
|
|
error,
|
|
onChange,
|
|
widget,
|
|
onDataFetched,
|
|
noDashboardsMEPProvider,
|
|
dashboardFilters,
|
|
location,
|
|
isWidgetInvalid,
|
|
}: Props) {
|
|
const [debouncedWidget, setDebouncedWidget] = useState(widget);
|
|
|
|
const previousWidget = usePrevious(widget);
|
|
|
|
// Disabling for now because we use debounce to avoid excessively hitting
|
|
// our endpoints, but useCallback wants an inline function and not one
|
|
// returned from debounce
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
const debounceWidget = useCallback(
|
|
debounce((value: Widget, shouldCancelUpdates: boolean) => {
|
|
if (shouldCancelUpdates) {
|
|
return;
|
|
}
|
|
setDebouncedWidget(value);
|
|
}, DEFAULT_DEBOUNCE_DURATION),
|
|
[]
|
|
);
|
|
|
|
useEffect(() => {
|
|
let shouldCancelUpdates = false;
|
|
|
|
if (!isEqual(previousWidget, widget)) {
|
|
debounceWidget(widget, shouldCancelUpdates);
|
|
}
|
|
|
|
return () => {
|
|
shouldCancelUpdates = true;
|
|
};
|
|
}, [widget, previousWidget, debounceWidget]);
|
|
|
|
const displayOptions = Object.keys(displayTypes).map(value => ({
|
|
label: displayTypes[value],
|
|
value,
|
|
}));
|
|
|
|
return (
|
|
<StyledBuildStep
|
|
title={t('Choose your visualization')}
|
|
description={t(
|
|
'This is a preview of how your widget will appear in the dashboard.'
|
|
)}
|
|
>
|
|
<FieldGroup error={error} inline={false} flexibleControlStateSize stacked>
|
|
<SelectControl
|
|
name="displayType"
|
|
options={displayOptions}
|
|
value={displayType}
|
|
onChange={(option: SelectValue<DisplayType>) => {
|
|
onChange(option.value);
|
|
}}
|
|
styles={{
|
|
singleValue: (provided: CSSProperties) => ({
|
|
...provided,
|
|
width: `calc(100% - ${space(1)})`,
|
|
}),
|
|
}}
|
|
/>
|
|
</FieldGroup>
|
|
<VisualizationWrapper displayType={displayType}>
|
|
<WidgetCard
|
|
organization={organization}
|
|
selection={pageFilters}
|
|
widget={debouncedWidget}
|
|
dashboardFilters={getDashboardFiltersFromURL(location) ?? dashboardFilters}
|
|
isEditingDashboard={false}
|
|
widgetLimitReached={false}
|
|
renderErrorMessage={errorMessage =>
|
|
typeof errorMessage === 'string' && (
|
|
<PanelAlert type="error">{errorMessage}</PanelAlert>
|
|
)
|
|
}
|
|
noLazyLoad
|
|
showStoredAlert
|
|
noDashboardsMEPProvider={noDashboardsMEPProvider}
|
|
isWidgetInvalid={isWidgetInvalid}
|
|
onDataFetched={onDataFetched}
|
|
/>
|
|
</VisualizationWrapper>
|
|
</StyledBuildStep>
|
|
);
|
|
}
|
|
|
|
const StyledBuildStep = styled(BuildStep)`
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
background: ${p => p.theme.background};
|
|
|
|
&::before {
|
|
margin-top: 1px;
|
|
}
|
|
`;
|
|
|
|
const VisualizationWrapper = styled('div')<{displayType: DisplayType}>`
|
|
padding-right: ${space(2)};
|
|
${WidgetCardPanel} {
|
|
height: initial;
|
|
}
|
|
${p =>
|
|
p.displayType === DisplayType.TABLE &&
|
|
css`
|
|
overflow: hidden;
|
|
${TableCell} {
|
|
/* 24px ActorContainer height + 16px top and bottom padding + 1px border = 41px */
|
|
height: 41px;
|
|
}
|
|
${WidgetCardPanel} {
|
|
/* total size of a table, if it would display 5 rows of content */
|
|
height: 301px;
|
|
}
|
|
`};
|
|
`;
|