Commit 6d402064 by Jack Westbrook Committed by GitHub

Dashboard: remove usage of Legacyforms (#28707)

* refactor(dashboard): remove usage of legacyform components in sharemodal

* refactor(dashboard): replace legacyform components

* refactor(dashboard): remove ng-if and correct typo in content of sharesnapshot

* feat(grafana-ui): set displayName prop for Switch component

* refactor(dashboard): migrate TimePickerSettings legacyform components

* refactor(queryoptions): migrate switch and input to nextgen components

* refactor(sharesnapshot): prefer InlineFieldRow over gf-form-group

* refactor(shareembed): styling fixes

* refactor(timepickersettings): prefer double bang over nullish coalescing operator

* fix(grafana-ui): switch uses id prop if passed in

* feat: connect labels and switches with ids
parent f7a5150d
...@@ -70,20 +70,19 @@ export const getSwitchStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -70,20 +70,19 @@ export const getSwitchStyles = stylesFactory((theme: GrafanaTheme) => {
transition: transform 0.2s cubic-bezier(0.19, 1, 0.22, 1); transition: transform 0.2s cubic-bezier(0.19, 1, 0.22, 1);
} }
} }
}
`, `,
}; };
}); });
export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>( export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
({ value, checked, disabled = false, onChange, ...inputProps }, ref) => { ({ value, checked, disabled = false, onChange, id, ...inputProps }, ref) => {
if (checked) { if (checked) {
deprecationWarning('Switch', 'checked prop', 'value'); deprecationWarning('Switch', 'checked prop', 'value');
} }
const theme = useTheme(); const theme = useTheme();
const styles = getSwitchStyles(theme); const styles = getSwitchStyles(theme);
const switchIdRef = useRef(uniqueId('switch-')); const switchIdRef = useRef(id ? id : uniqueId('switch-'));
return ( return (
<div className={cx(styles.switch)}> <div className={cx(styles.switch)}>
...@@ -103,3 +102,5 @@ export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>( ...@@ -103,3 +102,5 @@ export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
); );
} }
); );
Switch.displayName = 'Switch';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Input, LegacyForms, TimeZonePicker, Tooltip } from '@grafana/ui'; import { InlineField, Input, Switch, TimeZonePicker, Tooltip } from '@grafana/ui';
import { rangeUtil, TimeZone } from '@grafana/data'; import { rangeUtil, TimeZone } from '@grafana/data';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
...@@ -87,12 +87,9 @@ export class TimePickerSettings extends PureComponent<Props, State> { ...@@ -87,12 +87,9 @@ export class TimePickerSettings extends PureComponent<Props, State> {
</div> </div>
<div className="gf-form"> <div className="gf-form">
<LegacyForms.Switch <InlineField labelWidth={14} label="Hide time picker">
labelClass="width-7" <Switch value={!!this.props.timePickerHidden} onChange={this.onHideTimePickerChange} />
label="Hide time picker" </InlineField>
checked={this.props.timePickerHidden ?? false}
onChange={this.onHideTimePickerChange}
/>
</div> </div>
</div> </div>
</div> </div>
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { LegacyForms, Icon } from '@grafana/ui'; import { Select, Switch, Icon, InlineField } from '@grafana/ui';
const { Select, Switch } = LegacyForms;
import { SelectableValue } from '@grafana/data'; import { SelectableValue } from '@grafana/data';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { buildIframeHtml } from './utils'; import { buildIframeHtml } from './utils';
...@@ -82,24 +81,24 @@ export class ShareEmbed extends PureComponent<Props, State> { ...@@ -82,24 +81,24 @@ export class ShareEmbed extends PureComponent<Props, State> {
<Icon name="link" className="share-modal-big-icon" size="xxl" /> <Icon name="link" className="share-modal-big-icon" size="xxl" />
<div className="share-modal-content"> <div className="share-modal-content">
<div className="gf-form-group"> <div className="gf-form-group">
<InlineField labelWidth={24} label="Current time range">
<Switch <Switch
labelClass="width-12" id="share-current-time-range"
label="Current time range" value={useCurrentTimeRange}
checked={useCurrentTimeRange}
onChange={this.onUseCurrentTimeRangeChange} onChange={this.onUseCurrentTimeRangeChange}
/> />
</InlineField>
<InlineField labelWidth={24} label="Template variables">
<Switch <Switch
labelClass="width-12" id="share-template-variables"
label="Template variables" value={includeTemplateVars}
checked={includeTemplateVars}
onChange={this.onIncludeTemplateVarsChange} onChange={this.onIncludeTemplateVarsChange}
/> />
<div className="gf-form"> </InlineField>
<label className="gf-form-label width-12">Theme</label> <InlineField labelWidth={24} label="Theme">
<Select width={10} options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} /> <Select width={20} options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
</div> </InlineField>
</div> </div>
<p className="share-modal-info-text"> <p className="share-modal-info-text">
The html code below can be pasted and included in another web page. Unless anonymous access is enabled, The html code below can be pasted and included in another web page. Unless anonymous access is enabled,
the user viewing that page need to be signed into grafana for the graph to load. the user viewing that page need to be signed into grafana for the graph to load.
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { Button, LegacyForms, Icon } from '@grafana/ui'; import { Button, InlineField, Switch, Icon } from '@grafana/ui';
const { Switch } = LegacyForms;
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal'; import { DashboardExporter } from 'app/features/dashboard/components/DashExportModal';
import { appEvents } from 'app/core/core'; import { appEvents } from 'app/core/core';
...@@ -93,13 +92,9 @@ export class ShareExport extends PureComponent<Props, State> { ...@@ -93,13 +92,9 @@ export class ShareExport extends PureComponent<Props, State> {
<div className="share-modal-header"> <div className="share-modal-header">
<Icon name="cloud-upload" size="xxl" className="share-modal-big-icon" /> <Icon name="cloud-upload" size="xxl" className="share-modal-big-icon" />
<div className="share-modal-content"> <div className="share-modal-content">
<Switch <InlineField labelWidth={32} label="Export for sharing externally">
labelClass="width-16" <Switch value={shareExternally} onChange={this.onShareExternallyChange} />
label="Export for sharing externally" </InlineField>
checked={shareExternally}
onChange={this.onShareExternallyChange}
/>
<div className="gf-form-button-row"> <div className="gf-form-button-row">
<Button variant="primary" onClick={this.onSaveAsFile}> <Button variant="primary" onClick={this.onSaveAsFile}>
Save to file Save to file
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors'; import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import { LegacyForms, ClipboardButton, Icon, InfoBox, Input } from '@grafana/ui'; import { InlineField, Select, Switch, ClipboardButton, Icon, InfoBox, Input } from '@grafana/ui';
const { Select, Switch } = LegacyForms;
import { SelectableValue, PanelModel, AppEvents } from '@grafana/data'; import { SelectableValue, PanelModel, AppEvents } from '@grafana/data';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { buildImageUrl, buildShareUrl } from './utils'; import { buildImageUrl, buildShareUrl } from './utils';
...@@ -111,23 +110,26 @@ export class ShareLink extends PureComponent<Props, State> { ...@@ -111,23 +110,26 @@ export class ShareLink extends PureComponent<Props, State> {
Create a direct link to this dashboard or panel, customized with the options below. Create a direct link to this dashboard or panel, customized with the options below.
</p> </p>
<div className="gf-form-group"> <div className="gf-form-group">
<InlineField labelWidth={24} label="Current time range">
<Switch <Switch
labelClass="width-12" id="share-current-time-range"
label="Current time range" value={useCurrentTimeRange}
checked={useCurrentTimeRange}
onChange={this.onUseCurrentTimeRangeChange} onChange={this.onUseCurrentTimeRangeChange}
/> />
</InlineField>
<InlineField labelWidth={24} label="Template variables">
<Switch <Switch
labelClass="width-12" id="share-template-vars"
label="Template variables" value={includeTemplateVars}
checked={includeTemplateVars}
onChange={this.onIncludeTemplateVarsChange} onChange={this.onIncludeTemplateVarsChange}
/> />
<div className="gf-form"> </InlineField>
<label className="gf-form-label width-12">Theme</label> <InlineField labelWidth={24} label="Theme">
<Select width={10} options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} /> <Select width={20} options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
</div> </InlineField>
<Switch labelClass="width-12" label="Shorten URL" checked={useShortUrl} onChange={this.onUrlShorten} /> <InlineField labelWidth={24} label="Shorten URL">
<Switch id="share-shorten-url" value={useShortUrl} onChange={this.onUrlShorten} />
</InlineField>
</div> </div>
<div> <div>
<div className="gf-form-group"> <div className="gf-form-group">
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Button, ClipboardButton, Icon, Spinner, LegacyForms, LinkButton } from '@grafana/ui'; import {
Button,
ClipboardButton,
Icon,
Spinner,
Select,
Input,
LinkButton,
InlineField,
InlineFieldRow,
} from '@grafana/ui';
import { AppEvents, SelectableValue } from '@grafana/data'; import { AppEvents, SelectableValue } from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime'; import { getBackendSrv } from '@grafana/runtime';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
...@@ -7,8 +17,6 @@ import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; ...@@ -7,8 +17,6 @@ import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { appEvents } from 'app/core/core'; import { appEvents } from 'app/core/core';
import { VariableRefresh } from '../../../variables/types'; import { VariableRefresh } from '../../../variables/types';
const { Select, Input } = LegacyForms;
const snapshotApiUrl = '/api/snapshots'; const snapshotApiUrl = '/api/snapshots';
const expireOptions: Array<SelectableValue<number>> = [ const expireOptions: Array<SelectableValue<number>> = [
...@@ -221,27 +229,24 @@ export class ShareSnapshot extends PureComponent<Props, State> { ...@@ -221,27 +229,24 @@ export class ShareSnapshot extends PureComponent<Props, State> {
URL. Share wisely. URL. Share wisely.
</p> </p>
</div> </div>
<div className="gf-form-group share-modal-options"> <InlineFieldRow className="share-modal-options">
<div className="gf-form" ng-if="step === 1"> <InlineField labelWidth={24} label="Snapshot name">
<label className="gf-form-label width-12">Snapshot name</label> <Input width={30} value={snapshotName} onChange={this.onSnapshotNameChange} />
<Input width={15} value={snapshotName} onChange={this.onSnapshotNameChange} /> </InlineField>
</div> <InlineField labelWidth={24} label="Expire">
<div className="gf-form" ng-if="step === 1"> <Select width={30} options={expireOptions} value={selectedExpireOption} onChange={this.onExpireChange} />
<label className="gf-form-label width-12">Expire</label> </InlineField>
<Select width={15} options={expireOptions} value={selectedExpireOption} onChange={this.onExpireChange} /> </InlineFieldRow>
</div>
</div>
<p className="share-modal-info-text"> <p className="share-modal-info-text">
You may need to configure the timeout value if it takes a long time to collect your dashboard's metrics. You may need to configure the timeout value if it takes a long time to collect your dashboard's metrics.
</p> </p>
<div className="gf-form-group share-modal-options"> <InlineFieldRow className="share-modal-options">
<div className="gf-form"> <InlineField labelWidth={24} label="Timeout (seconds)">
<span className="gf-form-label width-12">Timeout (seconds)</span> <Input type="number" width={21} value={timeoutSeconds} onChange={this.onTimeoutChange} />
<Input type="number" width={15} value={timeoutSeconds} onChange={this.onTimeoutChange} /> </InlineField>
</div> </InlineFieldRow>
</div>
<div className="gf-form-button-row"> <div className="gf-form-button-row">
<Button className="width-10" variant="primary" disabled={isLoading} onClick={this.createSnapshot()}> <Button className="width-10" variant="primary" disabled={isLoading} onClick={this.createSnapshot()}>
...@@ -277,7 +282,7 @@ export class ShareSnapshot extends PureComponent<Props, State> { ...@@ -277,7 +282,7 @@ export class ShareSnapshot extends PureComponent<Props, State> {
</div> </div>
</div> </div>
<div className="pull-right" ng-if="step === 2" style={{ padding: '5px' }}> <div className="pull-right" style={{ padding: '5px' }}>
Did you make a mistake?{' '} Did you make a mistake?{' '}
<LinkButton variant="link" target="_blank" onClick={this.deleteSnapshot}> <LinkButton variant="link" target="_blank" onClick={this.deleteSnapshot}>
delete snapshot. delete snapshot.
...@@ -291,8 +296,8 @@ export class ShareSnapshot extends PureComponent<Props, State> { ...@@ -291,8 +296,8 @@ export class ShareSnapshot extends PureComponent<Props, State> {
return ( return (
<div className="share-modal-header"> <div className="share-modal-header">
<p className="share-modal-info-text"> <p className="share-modal-info-text">
The snapshot has now been deleted. If it you have already accessed it once, It might take up to an hour before The snapshot has now been deleted. If you have already accessed it once, it might take up to an hour before it
it is removed from browser caches or CDN caches. is removed from browser caches or CDN caches.
</p> </p>
</div> </div>
); );
......
...@@ -5,15 +5,7 @@ import React, { PureComponent, ChangeEvent, FocusEvent } from 'react'; ...@@ -5,15 +5,7 @@ import React, { PureComponent, ChangeEvent, FocusEvent } from 'react';
import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data'; import { rangeUtil, PanelData, DataSourceApi } from '@grafana/data';
// Components // Components
import { import { Switch, Input, InlineField, InlineFormLabel, stylesFactory } from '@grafana/ui';
EventsWithValidation,
LegacyInputStatus,
LegacyForms,
ValidationEvents,
InlineFormLabel,
stylesFactory,
} from '@grafana/ui';
const { Switch, Input } = LegacyForms;
// Types // Types
import { PanelModel } from '../state'; import { PanelModel } from '../state';
...@@ -21,18 +13,11 @@ import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOp ...@@ -21,18 +13,11 @@ import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOp
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { css } from 'emotion'; import { css } from 'emotion';
const timeRangeValidationEvents: ValidationEvents = { const timeRangeValidation = (value: string) => {
[EventsWithValidation.onBlur]: [
{
rule: value => {
if (!value) { if (!value) {
return true; return true;
} }
return rangeUtil.isValidTimeSpan(value); return rangeUtil.isValidTimeSpan(value);
},
errorMessage: 'Not a valid timespan',
},
],
}; };
const emptyToNull = (value: string) => { const emptyToNull = (value: string) => {
...@@ -53,6 +38,8 @@ interface State { ...@@ -53,6 +38,8 @@ interface State {
interval: string; interval: string;
hideTimeOverride: boolean; hideTimeOverride: boolean;
isOpen: boolean; isOpen: boolean;
relativeTimeIsValid: boolean;
timeShiftIsValid: boolean;
} }
export class QueryOptions extends PureComponent<Props, State> { export class QueryOptions extends PureComponent<Props, State> {
...@@ -67,6 +54,8 @@ export class QueryOptions extends PureComponent<Props, State> { ...@@ -67,6 +54,8 @@ export class QueryOptions extends PureComponent<Props, State> {
interval: props.panel.interval || '', interval: props.panel.interval || '',
hideTimeOverride: props.panel.hideTimeOverride || false, hideTimeOverride: props.panel.hideTimeOverride || false,
isOpen: false, isOpen: false,
relativeTimeIsValid: true,
timeShiftIsValid: true,
}; };
} }
...@@ -82,26 +71,30 @@ export class QueryOptions extends PureComponent<Props, State> { ...@@ -82,26 +71,30 @@ export class QueryOptions extends PureComponent<Props, State> {
}); });
}; };
onOverrideTime = (event: FocusEvent<HTMLInputElement>, status: LegacyInputStatus) => { onOverrideTime = (event: FocusEvent<HTMLInputElement>) => {
const { value } = event.target; const { value } = event.target;
const { panel } = this.props; const { panel } = this.props;
const emptyToNullValue = emptyToNull(value); const emptyToNullValue = emptyToNull(value);
const isValid = timeRangeValidation(value);
if (status === LegacyInputStatus.Valid && panel.timeFrom !== emptyToNullValue) { if (isValid && panel.timeFrom !== emptyToNullValue) {
panel.timeFrom = emptyToNullValue; panel.timeFrom = emptyToNullValue;
panel.refresh(); panel.refresh();
} }
this.setState({ relativeTimeIsValid: isValid });
}; };
onTimeShift = (event: FocusEvent<HTMLInputElement>, status: LegacyInputStatus) => { onTimeShift = (event: FocusEvent<HTMLInputElement>) => {
const { value } = event.target; const { value } = event.target;
const { panel } = this.props; const { panel } = this.props;
const emptyToNullValue = emptyToNull(value); const emptyToNullValue = emptyToNull(value);
const isValid = timeRangeValidation(value);
if (status === LegacyInputStatus.Valid && panel.timeShift !== emptyToNullValue) { if (isValid && panel.timeShift !== emptyToNullValue) {
panel.timeShift = emptyToNullValue; panel.timeShift = emptyToNullValue;
panel.refresh(); panel.refresh();
} }
this.setState({ timeShiftIsValid: isValid });
}; };
onToggleTimeOverride = () => { onToggleTimeOverride = () => {
...@@ -301,7 +294,7 @@ export class QueryOptions extends PureComponent<Props, State> { ...@@ -301,7 +294,7 @@ export class QueryOptions extends PureComponent<Props, State> {
} }
render() { render() {
const { hideTimeOverride } = this.state; const { hideTimeOverride, relativeTimeIsValid, timeShiftIsValid } = this.state;
const { relativeTime, timeShift, isOpen } = this.state; const { relativeTime, timeShift, isOpen } = this.state;
const styles = getStyles(); const styles = getStyles();
...@@ -327,8 +320,7 @@ export class QueryOptions extends PureComponent<Props, State> { ...@@ -327,8 +320,7 @@ export class QueryOptions extends PureComponent<Props, State> {
placeholder="1h" placeholder="1h"
onChange={this.onRelativeTimeChange} onChange={this.onRelativeTimeChange}
onBlur={this.onOverrideTime} onBlur={this.onOverrideTime}
validationEvents={timeRangeValidationEvents} invalid={!relativeTimeIsValid}
hideErrorMessage={true}
value={relativeTime} value={relativeTime}
/> />
</div> </div>
...@@ -341,19 +333,15 @@ export class QueryOptions extends PureComponent<Props, State> { ...@@ -341,19 +333,15 @@ export class QueryOptions extends PureComponent<Props, State> {
placeholder="1h" placeholder="1h"
onChange={this.onTimeShiftChange} onChange={this.onTimeShiftChange}
onBlur={this.onTimeShift} onBlur={this.onTimeShift}
validationEvents={timeRangeValidationEvents} invalid={!timeShiftIsValid}
hideErrorMessage={true}
value={timeShift} value={timeShift}
/> />
</div> </div>
{(timeShift || relativeTime) && ( {(timeShift || relativeTime) && (
<div className="gf-form-inline"> <div className="gf-form-inline">
<Switch <InlineField label="Hide time info" labelWidth={18}>
label="Hide time info" <Switch value={hideTimeOverride} onChange={this.onToggleTimeOverride} />
labelClass="width-9" </InlineField>
checked={hideTimeOverride}
onChange={this.onToggleTimeOverride}
/>
</div> </div>
)} )}
</QueryOperationRow> </QueryOperationRow>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment