|
@@ -1,19 +1,46 @@
|
|
|
-import {Fragment} from 'react';
|
|
|
+import {Fragment, useCallback, useState} from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
+import cloneDeep from 'lodash/cloneDeep';
|
|
|
|
|
|
import {Button} from 'sentry/components/button';
|
|
|
import Input from 'sentry/components/input';
|
|
|
-import {SearchQueryBuilder} from 'sentry/components/searchQueryBuilder';
|
|
|
import {IconAdd, IconDelete} from 'sentry/icons';
|
|
|
-import {t} from 'sentry/locale';
|
|
|
+import {t, tct} from 'sentry/locale';
|
|
|
import {space} from 'sentry/styles/space';
|
|
|
-import {DisplayType} from 'sentry/views/dashboards/types';
|
|
|
+import {
|
|
|
+ createOnDemandFilterWarning,
|
|
|
+ shouldDisplayOnDemandWidgetWarning,
|
|
|
+} from 'sentry/utils/onDemandMetrics';
|
|
|
+import useOrganization from 'sentry/utils/useOrganization';
|
|
|
+import usePageFilters from 'sentry/utils/usePageFilters';
|
|
|
+import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base';
|
|
|
+import {DisplayType, WidgetType} from 'sentry/views/dashboards/types';
|
|
|
import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
|
|
|
import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
|
|
|
import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
|
|
|
+import {getDiscoverDatasetFromWidgetType} from 'sentry/views/dashboards/widgetBuilder/utils';
|
|
|
+import {convertBuilderStateToWidget} from 'sentry/views/dashboards/widgetBuilder/utils/convertBuilderStateToWidget';
|
|
|
|
|
|
-function WidgetBuilderQueryFilterBuilder() {
|
|
|
+interface WidgetBuilderQueryFilterBuilderProps {
|
|
|
+ onQueryConditionChange: (valid: boolean) => void;
|
|
|
+}
|
|
|
+
|
|
|
+function WidgetBuilderQueryFilterBuilder({
|
|
|
+ onQueryConditionChange,
|
|
|
+}: WidgetBuilderQueryFilterBuilderProps) {
|
|
|
const {state, dispatch} = useWidgetBuilderContext();
|
|
|
+ const {selection} = usePageFilters();
|
|
|
+ const organization = useOrganization();
|
|
|
+
|
|
|
+ const [queryConditionValidity, setQueryConditionValidity] = useState<boolean[]>(() => {
|
|
|
+ // Make a validity entry for each query condition initially
|
|
|
+ return state.query?.map(() => true) ?? [];
|
|
|
+ });
|
|
|
+
|
|
|
+ const widgetType = state.dataset ?? WidgetType.ERRORS;
|
|
|
+ const datasetConfig = getDatasetConfig(state.dataset);
|
|
|
+
|
|
|
+ const widget = convertBuilderStateToWidget(state);
|
|
|
|
|
|
const canAddSearchConditions =
|
|
|
state.displayType !== DisplayType.TABLE &&
|
|
@@ -27,6 +54,45 @@ function WidgetBuilderQueryFilterBuilder() {
|
|
|
});
|
|
|
};
|
|
|
|
|
|
+ const handleClose = useCallback(
|
|
|
+ (queryIndex: number) => {
|
|
|
+ return (field: string, props: any) => {
|
|
|
+ const {validSearch} = props;
|
|
|
+ const nextQueryConditionValidity = cloneDeep(queryConditionValidity);
|
|
|
+ nextQueryConditionValidity[queryIndex] = validSearch;
|
|
|
+ setQueryConditionValidity(nextQueryConditionValidity);
|
|
|
+ onQueryConditionChange(nextQueryConditionValidity.every(validity => validity));
|
|
|
+ dispatch({
|
|
|
+ type: BuilderStateAction.SET_QUERY,
|
|
|
+ payload: state.query?.map((q, i) => (i === queryIndex ? field : q)) ?? [],
|
|
|
+ });
|
|
|
+ };
|
|
|
+ },
|
|
|
+ [dispatch, queryConditionValidity, state.query, onQueryConditionChange]
|
|
|
+ );
|
|
|
+
|
|
|
+ const handleRemove = useCallback(
|
|
|
+ (queryIndex: number) => () => {
|
|
|
+ queryConditionValidity.splice(queryIndex, 1);
|
|
|
+ setQueryConditionValidity(queryConditionValidity);
|
|
|
+ onQueryConditionChange(queryConditionValidity.every(validity => validity));
|
|
|
+ dispatch({
|
|
|
+ type: BuilderStateAction.SET_QUERY,
|
|
|
+ payload: state.query?.filter((_, i) => i !== queryIndex) ?? [],
|
|
|
+ });
|
|
|
+ },
|
|
|
+ [dispatch, queryConditionValidity, state.query, onQueryConditionChange]
|
|
|
+ );
|
|
|
+
|
|
|
+ const getOnDemandFilterWarning = createOnDemandFilterWarning(
|
|
|
+ tct(
|
|
|
+ 'We don’t routinely collect metrics from this property. However, we’ll do so [strong:once this widget has been saved.]',
|
|
|
+ {
|
|
|
+ strong: <strong />,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
return (
|
|
|
<Fragment>
|
|
|
<SectionHeader
|
|
@@ -42,14 +108,26 @@ function WidgetBuilderQueryFilterBuilder() {
|
|
|
/>
|
|
|
{!state.query?.length ? (
|
|
|
<QueryFieldRowWrapper key={0}>
|
|
|
- <QueryField
|
|
|
- query={''}
|
|
|
+ <datasetConfig.SearchBar
|
|
|
+ getFilterWarning={
|
|
|
+ shouldDisplayOnDemandWidgetWarning(
|
|
|
+ widget.queries[0],
|
|
|
+ widgetType,
|
|
|
+ organization
|
|
|
+ )
|
|
|
+ ? getOnDemandFilterWarning
|
|
|
+ : undefined
|
|
|
+ }
|
|
|
+ pageFilters={selection}
|
|
|
+ onClose={handleClose(0)}
|
|
|
onSearch={queryString => {
|
|
|
dispatch({
|
|
|
type: BuilderStateAction.SET_QUERY,
|
|
|
payload: [queryString],
|
|
|
});
|
|
|
}}
|
|
|
+ widgetQuery={widget.queries[0]}
|
|
|
+ dataset={getDiscoverDatasetFromWidgetType(widgetType)}
|
|
|
/>
|
|
|
{canAddSearchConditions && (
|
|
|
// TODO: Hook up alias to query hook when it's implemented
|
|
@@ -62,10 +140,20 @@ function WidgetBuilderQueryFilterBuilder() {
|
|
|
)}
|
|
|
</QueryFieldRowWrapper>
|
|
|
) : (
|
|
|
- state.query?.map((query, index) => (
|
|
|
+ state.query?.map((_, index) => (
|
|
|
<QueryFieldRowWrapper key={index}>
|
|
|
- <QueryField
|
|
|
- query={query}
|
|
|
+ <datasetConfig.SearchBar
|
|
|
+ getFilterWarning={
|
|
|
+ shouldDisplayOnDemandWidgetWarning(
|
|
|
+ widget.queries[index],
|
|
|
+ widgetType,
|
|
|
+ organization
|
|
|
+ )
|
|
|
+ ? getOnDemandFilterWarning
|
|
|
+ : undefined
|
|
|
+ }
|
|
|
+ pageFilters={selection}
|
|
|
+ onClose={handleClose(index)}
|
|
|
onSearch={queryString => {
|
|
|
dispatch({
|
|
|
type: BuilderStateAction.SET_QUERY,
|
|
@@ -73,6 +161,8 @@ function WidgetBuilderQueryFilterBuilder() {
|
|
|
state.query?.map((q, i) => (i === index ? queryString : q)) ?? [],
|
|
|
});
|
|
|
}}
|
|
|
+ widgetQuery={widget.queries[index]}
|
|
|
+ dataset={getDiscoverDatasetFromWidgetType(widgetType)}
|
|
|
/>
|
|
|
{canAddSearchConditions && (
|
|
|
// TODO: Hook up alias to query hook when it's implemented
|
|
@@ -84,14 +174,7 @@ function WidgetBuilderQueryFilterBuilder() {
|
|
|
/>
|
|
|
)}
|
|
|
{state.query && state.query?.length > 1 && canAddSearchConditions && (
|
|
|
- <DeleteButton
|
|
|
- onDelete={() =>
|
|
|
- dispatch({
|
|
|
- type: BuilderStateAction.SET_QUERY,
|
|
|
- payload: state.query?.filter((_, i) => i !== index) ?? [],
|
|
|
- })
|
|
|
- }
|
|
|
- />
|
|
|
+ <DeleteButton onDelete={handleRemove(index)} />
|
|
|
)}
|
|
|
</QueryFieldRowWrapper>
|
|
|
))
|
|
@@ -107,27 +190,6 @@ function WidgetBuilderQueryFilterBuilder() {
|
|
|
|
|
|
export default WidgetBuilderQueryFilterBuilder;
|
|
|
|
|
|
-function QueryField({
|
|
|
- query,
|
|
|
- onSearch,
|
|
|
-}: {
|
|
|
- onSearch: (query: string) => void;
|
|
|
- query: string;
|
|
|
-}) {
|
|
|
- return (
|
|
|
- <SearchQueryBuilder
|
|
|
- placeholder={t('Search')}
|
|
|
- filterKeys={{}}
|
|
|
- initialQuery={query ?? ''}
|
|
|
- onSearch={onSearch}
|
|
|
- searchSource={'widget_builder'}
|
|
|
- filterKeySections={[]}
|
|
|
- getTagValues={() => Promise.resolve([])}
|
|
|
- showUnsubmittedIndicator
|
|
|
- />
|
|
|
- );
|
|
|
-}
|
|
|
-
|
|
|
export function DeleteButton({onDelete}: {onDelete: () => void}) {
|
|
|
return (
|
|
|
<Button
|