Merge remote-tracking branch 'origin/main' into remove-search-v2
This commit is contained in:
@@ -180,12 +180,15 @@ func countAnnotationsV0V1(spec map[string]interface{}) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
annotationList, ok := annotations["list"].([]interface{})
|
||||
if !ok {
|
||||
return 0
|
||||
// Handle both []interface{} (from JSON unmarshaling) and []map[string]interface{} (from programmatic creation)
|
||||
if annotationList, ok := annotations["list"].([]interface{}); ok {
|
||||
return len(annotationList)
|
||||
}
|
||||
if annotationList, ok := annotations["list"].([]map[string]interface{}); ok {
|
||||
return len(annotationList)
|
||||
}
|
||||
|
||||
return len(annotationList)
|
||||
return 0
|
||||
}
|
||||
|
||||
// countLinksV0V1 counts dashboard links in v0alpha1 or v1beta1 dashboard spec
|
||||
@@ -194,12 +197,15 @@ func countLinksV0V1(spec map[string]interface{}) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
links, ok := spec["links"].([]interface{})
|
||||
if !ok {
|
||||
return 0
|
||||
// Handle both []interface{} (from JSON unmarshaling) and []map[string]interface{} (from programmatic creation)
|
||||
if links, ok := spec["links"].([]interface{}); ok {
|
||||
return len(links)
|
||||
}
|
||||
if links, ok := spec["links"].([]map[string]interface{}); ok {
|
||||
return len(links)
|
||||
}
|
||||
|
||||
return len(links)
|
||||
return 0
|
||||
}
|
||||
|
||||
// countVariablesV0V1 counts template variables in v0alpha1 or v1beta1 dashboard spec
|
||||
@@ -213,12 +219,15 @@ func countVariablesV0V1(spec map[string]interface{}) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
variableList, ok := templating["list"].([]interface{})
|
||||
if !ok {
|
||||
return 0
|
||||
// Handle both []interface{} (from JSON unmarshaling) and []map[string]interface{} (from programmatic creation)
|
||||
if variableList, ok := templating["list"].([]interface{}); ok {
|
||||
return len(variableList)
|
||||
}
|
||||
if variableList, ok := templating["list"].([]map[string]interface{}); ok {
|
||||
return len(variableList)
|
||||
}
|
||||
|
||||
return len(variableList)
|
||||
return 0
|
||||
}
|
||||
|
||||
// collectStatsV0V1 collects statistics from v0alpha1 or v1beta1 dashboard
|
||||
|
||||
@@ -17,8 +17,9 @@ export interface SparklineProps extends Themeable2 {
|
||||
showHighlights?: boolean;
|
||||
}
|
||||
|
||||
export const SparklineFn: React.FC<SparklineProps> = memo((props) => {
|
||||
export const Sparkline: React.FC<SparklineProps> = memo((props) => {
|
||||
const { sparkline, config: fieldConfig, theme, width, height, showHighlights } = props;
|
||||
|
||||
const { frame: alignedDataFrame, warning } = prepareSeries(sparkline, theme, fieldConfig, showHighlights);
|
||||
if (warning) {
|
||||
return null;
|
||||
@@ -30,14 +31,4 @@ export const SparklineFn: React.FC<SparklineProps> = memo((props) => {
|
||||
return <UPlotChart data={data} config={configBuilder} width={width} height={height} />;
|
||||
});
|
||||
|
||||
SparklineFn.displayName = 'Sparkline';
|
||||
|
||||
// we converted to function component above, but some apps extend Sparkline, so we need
|
||||
// to keep exporting a class component until those apps are all rolled out.
|
||||
// see https://github.com/grafana/app-observability-plugin/pull/2079
|
||||
// eslint-disable-next-line react-prefer-function-component/react-prefer-function-component
|
||||
export class Sparkline extends React.PureComponent<SparklineProps> {
|
||||
render() {
|
||||
return <SparklineFn {...this.props} />;
|
||||
}
|
||||
}
|
||||
Sparkline.displayName = 'Sparkline';
|
||||
|
||||
@@ -24,7 +24,7 @@ func (e *elasticsearchDataQuery) processQuery(q *Query, ms *es.MultiSearchReques
|
||||
filters.AddDateRangeFilter(defaultTimeField, to, from, es.DateFormatEpochMS)
|
||||
filters.AddQueryStringFilter(q.RawQuery, true)
|
||||
|
||||
if q.EditorType != nil && *q.EditorType == "code" && q.RawDSLQuery != "" {
|
||||
if q.EditorType != nil && *q.EditorType == "code" {
|
||||
cfg := backend.GrafanaConfigFromContext(e.ctx)
|
||||
if !cfg.FeatureToggles().IsEnabled("elasticsearchRawDSLQuery") {
|
||||
return backend.DownstreamError(fmt.Errorf("raw DSL query feature is disabled. Enable the elasticsearchRawDSLQuery feature toggle to use this query type"))
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
// isQueryWithError validates the query and returns an error if invalid
|
||||
func isQueryWithError(query *Query) error {
|
||||
// Skip validation for raw DSL queries because no easy way to see it is valid without just running it
|
||||
if query.EditorType != nil && *query.EditorType == "code" && query.RawDSLQuery != "" {
|
||||
if query.EditorType != nil && *query.EditorType == "code" {
|
||||
return nil
|
||||
}
|
||||
if len(query.BucketAggs) == 0 {
|
||||
|
||||
+27
-20
@@ -4,7 +4,7 @@ import { DataFrame, DataTransformerID, standardTransformersRegistry, Transformer
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Box, Button, Grid, Stack, Text } from '@grafana/ui';
|
||||
import { Box, Button, Stack, Text } from '@grafana/ui';
|
||||
import config from 'app/core/config';
|
||||
|
||||
import { SqlExpressionCard } from '../../../dashboard/components/TransformationsEditor/SqlExpressionCard';
|
||||
@@ -26,9 +26,6 @@ const TRANSFORMATION_IDS = [
|
||||
DataTransformerID.filterByValue,
|
||||
];
|
||||
|
||||
const GRID_COLUMNS_WITH_SQL = 5;
|
||||
const GRID_COLUMNS_WITHOUT_SQL = 4;
|
||||
|
||||
export function LegacyEmptyTransformationsMessage({ onShowPicker }: { onShowPicker: () => void }) {
|
||||
return (
|
||||
<Box alignItems="center" padding={4}>
|
||||
@@ -94,13 +91,25 @@ export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps)
|
||||
};
|
||||
|
||||
const showSqlCard = hasGoToQueries && config.featureToggles.sqlExpressions;
|
||||
const gridColumns = showSqlCard ? GRID_COLUMNS_WITH_SQL : GRID_COLUMNS_WITHOUT_SQL;
|
||||
|
||||
return (
|
||||
<Box alignItems="center" padding={4}>
|
||||
<Stack direction="column" alignItems="center" gap={4}>
|
||||
<Box padding={2}>
|
||||
<Stack direction="column" alignItems="start" gap={2}>
|
||||
<Stack direction="column" alignItems="start" gap={1}>
|
||||
<Text element="h3" textAlignment="start">
|
||||
<Trans i18nKey="transformations.empty.add-transformation-header">Add a Transformation</Trans>
|
||||
</Text>
|
||||
<Text element="p" textAlignment="start" color="secondary">
|
||||
<Trans i18nKey="transformations.empty.add-transformation-body">
|
||||
Transformations allow data to be changed in various ways before your visualization is shown.
|
||||
<br />
|
||||
This includes joining data together, renaming fields, making calculations, formatting data for display,
|
||||
and more.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Stack>
|
||||
{(hasAddTransformation || hasGoToQueries) && (
|
||||
<Grid columns={gridColumns} gap={1}>
|
||||
<Stack direction="row" gap={1} wrap>
|
||||
{showSqlCard && (
|
||||
<SqlExpressionCard
|
||||
name={t('dashboard-scene.empty-transformations-message.sql-name', 'Transform with SQL')}
|
||||
@@ -125,19 +134,17 @@ export function NewEmptyTransformationsMessage(props: EmptyTransformationsProps)
|
||||
data={props.data}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack direction="row" gap={2}>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="md"
|
||||
onClick={handleShowMoreClick}
|
||||
data-testid={selectors.components.Transforms.addTransformationButton}
|
||||
>
|
||||
<Trans i18nKey="dashboard-scene.empty-transformations-message.show-more">Show more</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="md"
|
||||
onClick={handleShowMoreClick}
|
||||
data-testid={selectors.components.Transforms.addTransformationButton}
|
||||
>
|
||||
<Trans i18nKey="dashboard-scene.empty-transformations-message.show-more">Show more</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -112,6 +112,37 @@ describe('PanelEditor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entering panel edit', () => {
|
||||
it('should clear edit pane selection', () => {
|
||||
pluginPromise = Promise.resolve(getPanelPlugin({ id: 'text', skipDataQuery: true }));
|
||||
|
||||
const panel = new VizPanel({
|
||||
key: 'panel-1',
|
||||
pluginId: 'text',
|
||||
title: 'original title',
|
||||
});
|
||||
const gridItem = new DashboardGridItem({ body: panel });
|
||||
const panelEditor = buildPanelEditScene(panel);
|
||||
const dashboard = new DashboardScene({
|
||||
editPanel: panelEditor,
|
||||
isEditing: true,
|
||||
$timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
|
||||
body: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
children: [gridItem],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
dashboard.state.editPane.selectObject(panel, panel.state.key!, { force: true });
|
||||
expect(dashboard.state.editPane.getSelection()).toBe(panel);
|
||||
|
||||
deactivate = activateFullSceneTree(dashboard);
|
||||
|
||||
expect(dashboard.state.editPane.getSelection()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When discarding', () => {
|
||||
it('should discard changes revert all changes', async () => {
|
||||
const { panelEditor, panel, dashboard } = await setup();
|
||||
|
||||
@@ -84,6 +84,11 @@ export class PanelEditor extends SceneObjectBase<PanelEditorState> {
|
||||
|
||||
private _activationHandler() {
|
||||
const panel = this.state.panelRef.resolve();
|
||||
const dashboard = getDashboardSceneFor(this);
|
||||
|
||||
// Clear any panel selection when entering panel edit mode.
|
||||
// Need to clear selection here since selection is activated when panel edit mode is entered through the panel actions menu. This causes sidebar panel editor to be open when exiting panel edit mode
|
||||
dashboard.state.editPane.clearSelection();
|
||||
|
||||
if (panel.state.pluginId === UNCONFIGURED_PANEL_PLUGIN_ID) {
|
||||
if (config.featureToggles.newVizSuggestions) {
|
||||
|
||||
+8
-54
@@ -1,7 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Card, Text, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Card, useStyles2 } from '@grafana/ui';
|
||||
import { getCardStyles } from './getCardStyles';
|
||||
|
||||
export interface SqlExpressionCardProps {
|
||||
name: string;
|
||||
@@ -12,60 +11,15 @@ export interface SqlExpressionCardProps {
|
||||
}
|
||||
|
||||
export function SqlExpressionCard({ name, description, imageUrl, onClick, testId }: SqlExpressionCardProps) {
|
||||
const styles = useStyles2(getSqlExpressionCardStyles);
|
||||
const styles = useStyles2(getCardStyles);
|
||||
|
||||
return (
|
||||
<Card className={styles.card} data-testid={testId} onClick={onClick} noMargin>
|
||||
<Card.Heading className={styles.heading}>
|
||||
<div className={styles.titleRow}>
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
</Card.Heading>
|
||||
<Card.Description className={styles.description}>
|
||||
<span>{description}</span>
|
||||
{imageUrl && (
|
||||
<span>
|
||||
<img className={styles.image} src={imageUrl} alt={name} />
|
||||
</span>
|
||||
)}
|
||||
<Card className={styles.baseCard} data-testid={testId} onClick={onClick} noMargin>
|
||||
<Card.Heading>{name}</Card.Heading>
|
||||
<Card.Description>
|
||||
<Text variant="bodySmall">{description}</Text>
|
||||
{imageUrl && <img className={styles.image} src={imageUrl} alt={name} />}
|
||||
</Card.Description>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function getSqlExpressionCardStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
card: css({
|
||||
gridTemplateRows: 'min-content 0 1fr 0',
|
||||
marginBottom: 0,
|
||||
}),
|
||||
heading: css({
|
||||
fontWeight: 400,
|
||||
'> button': {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}),
|
||||
titleRow: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'nowrap',
|
||||
width: '100%',
|
||||
}),
|
||||
description: css({
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
}),
|
||||
image: css({
|
||||
display: 'block',
|
||||
maxWidth: '100%',
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
+22
-84
@@ -1,35 +1,38 @@
|
||||
import { cx, css } from '@emotion/css';
|
||||
import { cx } from '@emotion/css';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
GrafanaTheme2,
|
||||
TransformerRegistryItem,
|
||||
TransformationApplicabilityLevels,
|
||||
standardTransformersRegistry,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Badge, Card, IconButton, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { Badge, Card, IconButton, Stack, Text, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { PluginStateInfo } from 'app/features/plugins/components/PluginStateInfo';
|
||||
|
||||
import { getCardStyles } from './getCardStyles';
|
||||
|
||||
export interface TransformationCardProps {
|
||||
transform: TransformerRegistryItem;
|
||||
data?: DataFrame[];
|
||||
fullWidth?: boolean;
|
||||
onClick: (id: string) => void;
|
||||
showIllustrations?: boolean;
|
||||
data?: DataFrame[];
|
||||
showPluginState?: boolean;
|
||||
showTags?: boolean;
|
||||
transform: TransformerRegistryItem;
|
||||
}
|
||||
|
||||
export function TransformationCard({
|
||||
transform,
|
||||
showIllustrations,
|
||||
onClick,
|
||||
data = [],
|
||||
fullWidth = false,
|
||||
onClick,
|
||||
showIllustrations,
|
||||
showPluginState = true,
|
||||
showTags = true,
|
||||
transform,
|
||||
}: TransformationCardProps) {
|
||||
const theme = useTheme2();
|
||||
const styles = useStyles2(getTransformationCardStyles);
|
||||
const styles = useStyles2(getCardStyles, fullWidth);
|
||||
|
||||
// Check to see if the transform is applicable to the given data
|
||||
let applicabilityScore = TransformationApplicabilityLevels.Applicable;
|
||||
@@ -47,7 +50,7 @@ export function TransformationCard({
|
||||
}
|
||||
}
|
||||
|
||||
const cardClasses = !isApplicable && data.length > 0 ? cx(styles.newCard, styles.cardDisabled) : styles.newCard;
|
||||
const cardClasses = cx(styles.baseCard, { [styles.cardDisabled]: !isApplicable });
|
||||
const imageUrl = theme.isDark ? transform.imageDark : transform.imageLight;
|
||||
const description = standardTransformersRegistry.getIfExists(transform.id)?.description;
|
||||
|
||||
@@ -58,15 +61,11 @@ export function TransformationCard({
|
||||
onClick={() => onClick(transform.id)}
|
||||
noMargin
|
||||
>
|
||||
<Card.Heading className={styles.heading}>
|
||||
<div className={styles.titleRow}>
|
||||
<span>{transform.name}</span>
|
||||
{showPluginState && (
|
||||
<span className={styles.pluginStateInfoWrapper}>
|
||||
<PluginStateInfo state={transform.state} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Card.Heading>
|
||||
<Stack alignItems="center" justifyContent="space-between">
|
||||
{transform.name}
|
||||
{showPluginState && <PluginStateInfo state={transform.state} />}
|
||||
</Stack>
|
||||
{showTags && transform.tags && transform.tags.size > 0 && (
|
||||
<div className={styles.tagsWrapper}>
|
||||
{Array.from(transform.tags).map((tag) => (
|
||||
@@ -75,74 +74,13 @@ export function TransformationCard({
|
||||
</div>
|
||||
)}
|
||||
</Card.Heading>
|
||||
<Card.Description className={styles.description}>
|
||||
<span>{description}</span>
|
||||
{showIllustrations && imageUrl && (
|
||||
<span>
|
||||
<img className={styles.image} src={imageUrl} alt={transform.name} />
|
||||
</span>
|
||||
)}
|
||||
<Card.Description>
|
||||
<Text variant="bodySmall">{description || ''}</Text>
|
||||
{showIllustrations && imageUrl && <img className={styles.image} src={imageUrl} alt={transform.name} />}
|
||||
{!isApplicable && applicabilityDescription !== null && (
|
||||
<IconButton className={styles.cardApplicableInfo} name="info-circle" tooltip={applicabilityDescription} />
|
||||
<IconButton className={styles.applicableInfoButton} name="info-circle" tooltip={applicabilityDescription} />
|
||||
)}
|
||||
</Card.Description>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function getTransformationCardStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
heading: css({
|
||||
fontWeight: 400,
|
||||
'> button': {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
}),
|
||||
titleRow: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'nowrap',
|
||||
width: '100%',
|
||||
}),
|
||||
description: css({
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
}),
|
||||
image: css({
|
||||
display: 'block',
|
||||
maxWidth: '100%',
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
cardDisabled: css({
|
||||
backgroundColor: theme.colors.action.disabledBackground,
|
||||
img: {
|
||||
filter: 'grayscale(100%)',
|
||||
opacity: 0.33,
|
||||
},
|
||||
}),
|
||||
cardApplicableInfo: css({
|
||||
position: 'absolute',
|
||||
bottom: theme.spacing(1),
|
||||
right: theme.spacing(1),
|
||||
}),
|
||||
newCard: css({
|
||||
gridTemplateRows: 'min-content 0 1fr 0',
|
||||
marginBottom: 0,
|
||||
}),
|
||||
pluginStateInfoWrapper: css({
|
||||
marginLeft: theme.spacing(0.5),
|
||||
}),
|
||||
tagsWrapper: css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(0.5),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
+5
-4
@@ -165,11 +165,12 @@ function TransformationsGrid({ showIllustrations, transformations, onClick, data
|
||||
<Grid columns={3} gap={1}>
|
||||
{transformations.map((transform) => (
|
||||
<TransformationCard
|
||||
key={transform.id}
|
||||
transform={transform}
|
||||
showIllustrations={showIllustrations}
|
||||
onClick={onClick}
|
||||
data={data}
|
||||
fullWidth
|
||||
key={transform.id}
|
||||
onClick={onClick}
|
||||
showIllustrations={showIllustrations}
|
||||
transform={transform}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export const getCardStyles = (theme: GrafanaTheme2, fullWidth?: boolean) => ({
|
||||
baseCard: css({
|
||||
maxWidth: fullWidth ? 'none' : '200px',
|
||||
width: fullWidth ? '100%' : 'auto',
|
||||
marginBottom: 0,
|
||||
}),
|
||||
image: css({
|
||||
display: 'block',
|
||||
maxWidth: '100%',
|
||||
marginTop: theme.spacing(2),
|
||||
}),
|
||||
cardDisabled: css({
|
||||
backgroundColor: theme.colors.action.disabledBackground,
|
||||
img: {
|
||||
filter: 'grayscale(100%)',
|
||||
opacity: 0.33,
|
||||
},
|
||||
}),
|
||||
applicableInfoButton: css({
|
||||
position: 'absolute',
|
||||
bottom: theme.spacing(1),
|
||||
right: theme.spacing(1),
|
||||
}),
|
||||
tagsWrapper: css({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(0.5),
|
||||
marginTop: theme.spacing(0.5),
|
||||
}),
|
||||
});
|
||||
+24
-1
@@ -7,7 +7,7 @@ import {
|
||||
import { defaultBucketAgg } from '../../../../queryDef';
|
||||
import { reducerTester } from '../../../reducerTester';
|
||||
import { changeMetricType } from '../../MetricAggregationsEditor/state/actions';
|
||||
import { initQuery } from '../../state';
|
||||
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
||||
import { bucketAggregationConfig } from '../utils';
|
||||
|
||||
import {
|
||||
@@ -180,4 +180,27 @@ describe('Bucket Aggregations Reducer', () => {
|
||||
.thenStateShouldEqual([bucketAgg]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When switching editor type', () => {
|
||||
it('Should reset bucket aggregations to default when switching editor types', () => {
|
||||
const defaultTimeField = '@timestamp';
|
||||
const initialState: BucketAggregation[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'date_histogram',
|
||||
field: '@timestamp',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'terms',
|
||||
field: 'status',
|
||||
},
|
||||
];
|
||||
|
||||
reducerTester<ElasticsearchDataQuery['bucketAggs']>()
|
||||
.givenReducer(createReducer(defaultTimeField), initialState)
|
||||
.whenActionIsDispatched(changeEditorTypeAndResetQuery('code'))
|
||||
.thenStateShouldEqual([{ ...defaultBucketAgg('2'), field: defaultTimeField }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+6
-1
@@ -6,7 +6,7 @@ import { defaultBucketAgg } from '../../../../queryDef';
|
||||
import { removeEmpty } from '../../../../utils';
|
||||
import { changeMetricType } from '../../MetricAggregationsEditor/state/actions';
|
||||
import { metricAggregationConfig } from '../../MetricAggregationsEditor/utils';
|
||||
import { initQuery } from '../../state';
|
||||
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
||||
import { bucketAggregationConfig } from '../utils';
|
||||
|
||||
import {
|
||||
@@ -87,6 +87,11 @@ export const createReducer =
|
||||
return state;
|
||||
}
|
||||
|
||||
if (changeEditorTypeAndResetQuery.match(action)) {
|
||||
// Returns the default bucket agg. We will always want to set the default when switching types
|
||||
return [{ ...defaultBucketAgg('2'), field: defaultTimeField }];
|
||||
}
|
||||
|
||||
if (changeBucketAggregationSetting.match(action)) {
|
||||
return state!.map((bucketAgg) => {
|
||||
if (bucketAgg.id !== action.payload.bucketAgg.id) {
|
||||
|
||||
+23
-1
@@ -7,7 +7,7 @@ import {
|
||||
|
||||
import { defaultMetricAgg } from '../../../../queryDef';
|
||||
import { reducerTester } from '../../../reducerTester';
|
||||
import { initQuery } from '../../state';
|
||||
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
||||
import { metricAggregationConfig } from '../utils';
|
||||
|
||||
import {
|
||||
@@ -248,4 +248,26 @@ describe('Metric Aggregations Reducer', () => {
|
||||
.whenActionIsDispatched(initQuery())
|
||||
.thenStateShouldEqual([defaultMetricAgg('1')]);
|
||||
});
|
||||
|
||||
describe('When switching editor type', () => {
|
||||
it('Should reset to single default metric when switching to code editor', () => {
|
||||
const initialState: MetricAggregation[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'avg',
|
||||
field: 'value',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'max',
|
||||
field: 'value',
|
||||
},
|
||||
];
|
||||
|
||||
reducerTester<ElasticsearchDataQuery['metrics']>()
|
||||
.givenReducer(reducer, initialState)
|
||||
.whenActionIsDispatched(changeEditorTypeAndResetQuery('code'))
|
||||
.thenStateShouldEqual([defaultMetricAgg('1')]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+6
-1
@@ -4,7 +4,7 @@ import { ElasticsearchDataQuery, MetricAggregation } from 'app/plugins/datasourc
|
||||
|
||||
import { defaultMetricAgg, queryTypeToMetricType } from '../../../../queryDef';
|
||||
import { removeEmpty } from '../../../../utils';
|
||||
import { initQuery } from '../../state';
|
||||
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
||||
import { isMetricAggregationWithMeta, isMetricAggregationWithSettings, isPipelineAggregation } from '../aggregations';
|
||||
import { getChildren, metricAggregationConfig } from '../utils';
|
||||
|
||||
@@ -65,6 +65,11 @@ export const reducer = (
|
||||
});
|
||||
}
|
||||
|
||||
if (changeEditorTypeAndResetQuery.match(action)) {
|
||||
// Reset to default metric when switching to editor types
|
||||
return [defaultMetricAgg('1')];
|
||||
}
|
||||
|
||||
if (changeMetricField.match(action)) {
|
||||
return state!.map((metric) => {
|
||||
if (metric.id !== action.payload.id) {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { ElasticsearchDataQuery } from '../../dataquery.gen';
|
||||
import { reducerTester } from '../reducerTester';
|
||||
|
||||
import { aliasPatternReducer, changeAliasPattern, changeQuery, initQuery, queryReducer } from './state';
|
||||
import {
|
||||
aliasPatternReducer,
|
||||
changeAliasPattern,
|
||||
changeEditorTypeAndResetQuery,
|
||||
changeQuery,
|
||||
initQuery,
|
||||
queryReducer,
|
||||
rawDSLQueryReducer,
|
||||
} from './state';
|
||||
|
||||
describe('Query Reducer', () => {
|
||||
describe('On Init', () => {
|
||||
@@ -42,6 +50,17 @@ describe('Query Reducer', () => {
|
||||
.whenActionIsDispatched({ type: 'THIS ACTION SHOULD NOT HAVE ANY EFFECT IN THIS REDUCER' })
|
||||
.thenStateShouldEqual(initialState);
|
||||
});
|
||||
|
||||
describe('When switching editor type', () => {
|
||||
it('Should clear query when switching editor types', () => {
|
||||
const initialQuery: ElasticsearchDataQuery['query'] = 'Some lucene query';
|
||||
|
||||
reducerTester<ElasticsearchDataQuery['query']>()
|
||||
.givenReducer(queryReducer, initialQuery)
|
||||
.whenActionIsDispatched(changeEditorTypeAndResetQuery('code'))
|
||||
.thenStateShouldEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Alias Pattern Reducer', () => {
|
||||
@@ -62,4 +81,26 @@ describe('Alias Pattern Reducer', () => {
|
||||
.whenActionIsDispatched({ type: 'THIS ACTION SHOULD NOT HAVE ANY EFFECT IN THIS REDUCER' })
|
||||
.thenStateShouldEqual(initialState);
|
||||
});
|
||||
|
||||
describe('When switching editor type', () => {
|
||||
it('Should clear alias when switching editor types', () => {
|
||||
const initialAlias: ElasticsearchDataQuery['alias'] = 'Some alias pattern';
|
||||
|
||||
reducerTester<ElasticsearchDataQuery['alias']>()
|
||||
.givenReducer(aliasPatternReducer, initialAlias)
|
||||
.whenActionIsDispatched(changeEditorTypeAndResetQuery('code'))
|
||||
.thenStateShouldEqual('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Raw DSL Query Reducer', () => {
|
||||
it('Should clear raw DSL query when switching editor types', () => {
|
||||
const initialRawQuery: ElasticsearchDataQuery['rawDSLQuery'] = '{"query": {"match_all": {}}}';
|
||||
|
||||
reducerTester<ElasticsearchDataQuery['rawDSLQuery']>()
|
||||
.givenReducer(rawDSLQueryReducer, initialRawQuery)
|
||||
.whenActionIsDispatched(changeEditorTypeAndResetQuery('builder'))
|
||||
.thenStateShouldEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,6 +58,10 @@ export const aliasPatternReducer = (prevAliasPattern: ElasticsearchDataQuery['al
|
||||
return action.payload;
|
||||
}
|
||||
|
||||
if (changeEditorTypeAndResetQuery.match(action)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (initQuery.match(action)) {
|
||||
return prevAliasPattern || '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user