Alerting: adding a time picker for selecting relative time. (#33689)

* adding placeholder for relative time range.

* fixed story.

* added basic structure to handle open/close of time range picker.

* removed section from TimeOptions since it isn't used any where.

* adding mapper and tests

* move relativetimepicker to its own dir

* added some simple tests.

* changed test.

* use relativetimerangeinput

* redo state management

* refactored the tests.

* replace timerange with relativetimerange

* wip

* wip

* did some refactoring.

* refactored time option formatting.

* added proper formatting and display of time range.

* add relative time description, slight refactor of height

* fixed incorrect import.

* added validator and changed formatting.

* removed unused dep.

* reverted back to internal function.

* fixed display of relative time range picker.

* fixed failing tests.

* fixed parsing issue.

* fixed position of time range picker.

* some more refactorings.

* fixed validation of really big values.

* added another test.

Co-authored-by: Peter Holmberg <peter.hlmbrg@gmail.com>
This commit is contained in:
Marcus Andersson
2021-05-12 17:51:31 +02:00
committed by GitHub
parent 2459a0ceb5
commit 07ef4060a3
18 changed files with 645 additions and 214 deletions

View File

@@ -1,5 +1,5 @@
// Libraries
import React, { PureComponent } from 'react';
import React, { PureComponent, ReactNode } from 'react';
import classNames from 'classnames';
import { has, cloneDeep } from 'lodash';
// Utils & Services
@@ -35,10 +35,9 @@ interface Props {
queries: DataQuery[];
id: string;
index: number;
timeRange?: TimeRange;
dataSource: DataSourceInstanceSettings;
onChangeDataSource?: (dsSettings: DataSourceInstanceSettings) => void;
onChangeTimeRange?: (timeRange: TimeRange) => void;
renderHeaderExtras?: () => ReactNode;
onAddQuery: (query: DataQuery) => void;
onRemoveQuery: (query: DataQuery) => void;
onChange: (query: DataQuery) => void;
@@ -304,20 +303,19 @@ export class QueryEditorRow extends PureComponent<Props, State> {
};
renderHeader = (props: QueryOperationRowRenderProps) => {
const { query, dataSource, onChangeDataSource, onChange, queries, onChangeTimeRange, timeRange } = this.props;
const { query, dataSource, onChangeDataSource, onChange, queries, renderHeaderExtras } = this.props;
return (
<QueryEditorRowHeader
query={query}
queries={queries}
onChangeTimeRange={onChangeTimeRange}
timeRange={timeRange}
onChangeDataSource={onChangeDataSource}
dataSource={dataSource}
disabled={query.hide}
onClick={(e) => this.onToggleEditMode(e, props)}
onChange={onChange}
collapsedText={!props.isOpen ? this.renderCollapsedText() : null}
renderExtras={renderHeaderExtras}
/>
);
};

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { Props, QueryEditorRowHeader } from './QueryEditorRowHeader';
import { DataSourceInstanceSettings, dateTime } from '@grafana/data';
import { DataSourceInstanceSettings } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => {
@@ -58,41 +58,6 @@ describe('QueryEditorRowHeader', () => {
expect(screen.queryByLabelText(selectors.components.DataSourcePicker.container)).toBeNull();
});
it('should show time range picker when callback and value is passed', async () => {
renderScenario({
onChangeTimeRange: () => {},
timeRange: {
from: dateTime(),
to: dateTime(),
raw: { from: 'now', to: 'now' },
},
});
expect(screen.queryByLabelText(selectors.components.TimePicker.openButton)).not.toBeNull();
});
it('should not show time range picker when no value is passed', async () => {
renderScenario({
onChangeTimeRange: () => {},
timeRange: undefined,
});
expect(screen.queryByLabelText(selectors.components.DataSourcePicker.container)).toBeNull();
});
it('should not show time range picker when no callback is passed', async () => {
renderScenario({
onChangeTimeRange: undefined,
timeRange: {
from: dateTime(),
to: dateTime(),
raw: { from: 'now', to: 'now' },
},
});
expect(screen.queryByLabelText(selectors.components.DataSourcePicker.container)).toBeNull();
});
});
function renderScenario(overrides: Partial<Props>) {

View File

@@ -1,26 +1,24 @@
import React, { useState } from 'react';
import React, { ReactNode, useState } from 'react';
import { css, cx } from '@emotion/css';
import { DataQuery, DataSourceInstanceSettings, GrafanaTheme, TimeRange } from '@grafana/data';
import { DataQuery, DataSourceInstanceSettings, GrafanaTheme } from '@grafana/data';
import { DataSourcePicker } from '@grafana/runtime';
import { Icon, Input, FieldValidationMessage, TimeRangeInput, useStyles } from '@grafana/ui';
import { Icon, Input, FieldValidationMessage, useStyles } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
import { ExpressionDatasourceUID } from '../../expressions/ExpressionDatasource';
export interface Props {
query: DataQuery;
queries: DataQuery[];
disabled?: boolean;
timeRange?: TimeRange;
dataSource: DataSourceInstanceSettings;
renderExtras?: () => ReactNode;
onChangeDataSource?: (settings: DataSourceInstanceSettings) => void;
onChangeTimeRange?: (timeRange: TimeRange) => void;
onChange: (query: DataQuery) => void;
onClick: (e: React.MouseEvent) => void;
collapsedText: string | null;
}
export const QueryEditorRowHeader: React.FC<Props> = (props) => {
const { dataSource, onChangeDataSource, disabled, query, queries, onClick, onChange, collapsedText } = props;
const { query, queries, onClick, onChange, collapsedText, renderExtras, disabled } = props;
const styles = useStyles(getStyles);
const [isEditing, setIsEditing] = useState<boolean>(false);
@@ -112,9 +110,9 @@ export const QueryEditorRowHeader: React.FC<Props> = (props) => {
{validationError && <FieldValidationMessage horizontal>{validationError}</FieldValidationMessage>}
</>
)}
<PickerRenderer {...props} />
{dataSource && !onChangeDataSource && <em className={styles.contextInfo}> ({dataSource.name})</em>}
{disabled && <em className={styles.contextInfo}> Disabled</em>}
{renderDataSource(props, styles)}
{renderExtras && <div className={styles.itemWrapper}>{renderExtras()}</div>}
{disabled && <em className={styles.contextInfo}>Disabled</em>}
{collapsedText && (
<div className={styles.collapsedText} onClick={onClick}>
@@ -125,22 +123,16 @@ export const QueryEditorRowHeader: React.FC<Props> = (props) => {
);
};
const PickerRenderer: React.FC<Props> = (props) => {
const { onChangeTimeRange, timeRange, onChangeDataSource, dataSource } = props;
const styles = useStyles(getStyles);
const renderDataSource = (props: Props, styles: ReturnType<typeof getStyles>): ReactNode => {
const { dataSource, onChangeDataSource } = props;
if (!onChangeTimeRange && !onChangeDataSource) {
return null;
}
if (dataSource.uid === ExpressionDatasourceUID) {
return null;
if (!onChangeDataSource) {
return <em className={styles.contextInfo}>({dataSource.name})</em>;
}
return (
<div className={styles.pickerWrapper}>
{onChangeDataSource && <DataSourcePicker current={dataSource.name} onChange={onChangeDataSource} />}
{onChangeTimeRange && timeRange && <TimeRangeInput onChange={onChangeTimeRange} value={timeRange} />}
<div className={styles.itemWrapper}>
<DataSourcePicker current={dataSource.name} onChange={onChangeDataSource} />
</div>
);
};
@@ -222,9 +214,9 @@ const getStyles = (theme: GrafanaTheme) => {
color: ${theme.colors.textWeak};
padding-left: 10px;
`,
pickerWrapper: css`
itemWrapper: css`
display: flex;
margin-left: 8px;
margin-left: 4px;
`,
};
};