Преглед на файлове

Refactor: Make SelectOptionItem a generic type to enable select value typing (#16718)

* Make SelectOptionItem a generic type to enable select value typing
* TS ignores added because of optional value on Select items (it's no longer any)
Dominik Prokop преди 6 години
родител
ревизия
19e824006a
променени са 26 файла, в които са добавени 71 реда и са изтрити 61 реда
  1. 3 2
      packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx
  2. 3 3
      packages/grafana-ui/src/components/Select/ButtonSelect.story.tsx
  3. 6 6
      packages/grafana-ui/src/components/Select/ButtonSelect.tsx
  4. 13 13
      packages/grafana-ui/src/components/Select/Select.tsx
  5. 9 8
      packages/grafana-ui/src/components/SingleStatShared/SingleStatValueEditor.tsx
  6. 3 3
      packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx
  7. 4 1
      packages/grafana-ui/src/components/TimePicker/TimePicker.tsx
  8. 1 0
      packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx
  9. 1 1
      packages/grafana-ui/src/utils/string.ts
  10. 1 1
      public/app/core/components/PermissionList/AddPermission.tsx
  11. 1 1
      public/app/core/components/Select/DataSourcePicker.tsx
  12. 2 2
      public/app/core/components/Select/MetricSelect.tsx
  13. 1 1
      public/app/features/teams/TeamMemberRow.test.tsx
  14. 2 2
      public/app/features/teams/TeamMemberRow.tsx
  15. 1 1
      public/app/plugins/datasource/input/InputQueryEditor.tsx
  16. 1 1
      public/app/plugins/datasource/stackdriver/components/Alignments.tsx
  17. 1 1
      public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx
  18. 1 1
      public/app/plugins/datasource/testdata/QueryEditor.tsx
  19. 1 1
      public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx
  20. 2 2
      public/app/plugins/panel/bargauge/types.ts
  21. 1 1
      public/app/plugins/panel/gauge/GaugePanelEditor.tsx
  22. 1 1
      public/app/plugins/panel/piechart/PieChartPanelEditor.tsx
  23. 5 3
      public/app/plugins/panel/singlestat2/FontSizeEditor.tsx
  24. 1 1
      public/app/plugins/panel/singlestat2/SingleStatEditor.tsx
  25. 4 3
      public/app/plugins/panel/text2/TextPanelEditor.tsx
  26. 2 1
      public/app/plugins/panel/text2/types.ts

+ 3 - 2
packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx

@@ -33,15 +33,16 @@ export class RefreshPicker extends PureComponent<Props> {
     return false;
   };
 
-  intervalsToOptions = (intervals: string[] = defaultIntervals): SelectOptionItem[] => {
+  intervalsToOptions = (intervals: string[] = defaultIntervals): Array<SelectOptionItem<string>> => {
     const options = intervals.map(interval => ({ label: interval, value: interval }));
     options.unshift(offOption);
     return options;
   };
 
-  onChangeSelect = (item: SelectOptionItem) => {
+  onChangeSelect = (item: SelectOptionItem<string>) => {
     const { onIntervalChanged } = this.props;
     if (onIntervalChanged) {
+      // @ts-ignore
       onIntervalChanged(item.value);
     }
   };

+ 3 - 3
packages/grafana-ui/src/components/Select/ButtonSelect.story.tsx

@@ -12,9 +12,9 @@ const ButtonSelectStories = storiesOf('UI/Select/ButtonSelect', module);
 ButtonSelectStories.addDecorator(withCenteredStory).addDecorator(withKnobs);
 
 ButtonSelectStories.add('default', () => {
-  const intialState: SelectOptionItem = { label: 'A label', value: 'A value' };
-  const value = object<SelectOptionItem>('Selected Value:', intialState);
-  const options = object<SelectOptionItem[]>('Options:', [
+  const intialState: SelectOptionItem<string> = { label: 'A label', value: 'A value' };
+  const value = object<SelectOptionItem<string>>('Selected Value:', intialState);
+  const options = object<Array<SelectOptionItem<string>>>('Options:', [
     intialState,
     { label: 'Another label', value: 'Another value' },
   ]);

+ 6 - 6
packages/grafana-ui/src/components/Select/ButtonSelect.tsx

@@ -27,23 +27,23 @@ const ButtonComponent = (buttonProps: ButtonComponentProps) => (props: any) => {
   );
 };
 
-export interface Props {
+export interface Props<T> {
   className: string | undefined;
-  options: SelectOptionItem[];
-  value: SelectOptionItem;
+  options: Array<SelectOptionItem<T>>;
+  value: SelectOptionItem<T>;
   label?: string;
   iconClass?: string;
   components?: any;
   maxMenuHeight?: number;
-  onChange: (item: SelectOptionItem) => void;
+  onChange: (item: SelectOptionItem<T>) => void;
   tooltipContent?: PopperContent<any>;
   isMenuOpen?: boolean;
   onOpenMenu?: () => void;
   onCloseMenu?: () => void;
 }
 
-export class ButtonSelect extends PureComponent<Props> {
-  onChange = (item: SelectOptionItem) => {
+export class ButtonSelect<T> extends PureComponent<Props<T>> {
+  onChange = (item: SelectOptionItem<T>) => {
     const { onChange } = this.props;
     onChange(item);
   };

+ 13 - 13
packages/grafana-ui/src/components/Select/Select.tsx

@@ -20,22 +20,22 @@ import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
 import { PopperContent } from '@grafana/ui/src/components/Tooltip/PopperController';
 import { Tooltip } from '@grafana/ui';
 
-export interface SelectOptionItem {
+export interface SelectOptionItem<T> {
   label?: string;
-  value?: any;
+  value?: T;
   imgUrl?: string;
   description?: string;
   [key: string]: any;
 }
 
-export interface CommonProps {
+export interface CommonProps<T> {
   defaultValue?: any;
-  getOptionLabel?: (item: SelectOptionItem) => string;
-  getOptionValue?: (item: SelectOptionItem) => string;
-  onChange: (item: SelectOptionItem) => {} | void;
+  getOptionLabel?: (item: SelectOptionItem<T>) => string;
+  getOptionValue?: (item: SelectOptionItem<T>) => string;
+  onChange: (item: SelectOptionItem<T>) => {} | void;
   placeholder?: string;
   width?: number;
-  value?: SelectOptionItem;
+  value?: SelectOptionItem<T>;
   className?: string;
   isDisabled?: boolean;
   isSearchable?: boolean;
@@ -55,13 +55,13 @@ export interface CommonProps {
   onCloseMenu?: () => void;
 }
 
-export interface SelectProps {
-  options: SelectOptionItem[];
+export interface SelectProps<T> {
+  options: Array<SelectOptionItem<T>>;
 }
 
-interface AsyncProps {
+interface AsyncProps<T> {
   defaultOptions: boolean;
-  loadOptions: (query: string) => Promise<SelectOptionItem[]>;
+  loadOptions: (query: string) => Promise<Array<SelectOptionItem<T>>>;
   loadingMessage?: () => string;
 }
 
@@ -95,7 +95,7 @@ export const MenuList = (props: any) => {
   );
 };
 
-export class Select extends PureComponent<CommonProps & SelectProps> {
+export class Select<T> extends PureComponent<CommonProps<T> & SelectProps<T>> {
   static defaultProps = {
     width: null,
     className: '',
@@ -201,7 +201,7 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
   }
 }
 
-export class AsyncSelect extends PureComponent<CommonProps & AsyncProps> {
+export class AsyncSelect<T> extends PureComponent<CommonProps<T> & AsyncProps<T>> {
   static defaultProps = {
     width: null,
     className: '',

+ 9 - 8
packages/grafana-ui/src/components/SingleStatShared/SingleStatValueEditor.tsx

@@ -18,39 +18,40 @@ import { SingleStatValueOptions } from './shared';
 const labelWidth = 6;
 
 export interface Props {
-  options: SingleStatValueOptions;
+  value: SingleStatValueOptions;
   onChange: (valueOptions: SingleStatValueOptions) => void;
 }
 
 export class SingleStatValueEditor extends PureComponent<Props> {
-  onUnitChange = (unit: SelectOptionItem) => this.props.onChange({ ...this.props.options, unit: unit.value });
+  // @ts-ignore
+  onUnitChange = (unit: SelectOptionItem<string>) => this.props.onChange({ ...this.props.value, unit: unit.value });
 
   onStatsChange = (stats: string[]) => {
     const stat = stats[0] || StatID.mean;
-    this.props.onChange({ ...this.props.options, stat });
+    this.props.onChange({ ...this.props.value, stat });
   };
 
   onDecimalChange = (event: ChangeEvent<HTMLInputElement>) => {
     if (!isNaN(parseInt(event.target.value, 10))) {
       this.props.onChange({
-        ...this.props.options,
+        ...this.props.value,
         decimals: parseInt(event.target.value, 10),
       });
     } else {
       this.props.onChange({
-        ...this.props.options,
+        ...this.props.value,
         decimals: null,
       });
     }
   };
 
   onPrefixChange = (event: ChangeEvent<HTMLInputElement>) =>
-    this.props.onChange({ ...this.props.options, prefix: event.target.value });
+    this.props.onChange({ ...this.props.value, prefix: event.target.value });
   onSuffixChange = (event: ChangeEvent<HTMLInputElement>) =>
-    this.props.onChange({ ...this.props.options, suffix: event.target.value });
+    this.props.onChange({ ...this.props.value, suffix: event.target.value });
 
   render() {
-    const { stat, unit, decimals, prefix, suffix } = this.props.options;
+    const { stat, unit, decimals, prefix, suffix } = this.props.value;
 
     let decimalsString = '';
     if (decimals !== null && decimals !== undefined && Number.isFinite(decimals as number)) {

+ 3 - 3
packages/grafana-ui/src/components/StatsPicker/StatsPicker.tsx

@@ -54,12 +54,12 @@ export class StatsPicker extends PureComponent<Props> {
     }
   };
 
-  onSelectionChange = (item: SelectOptionItem) => {
+  onSelectionChange = (item: SelectOptionItem<string>) => {
     const { onChange } = this.props;
     if (isArray(item)) {
       onChange(item.map(v => v.value));
     } else {
-      onChange([item.value]);
+      onChange(item.value ? [item.value] : []);
     }
   };
 
@@ -73,7 +73,7 @@ export class StatsPicker extends PureComponent<Props> {
       };
     });
 
-    const value: SelectOptionItem[] = options.filter(option => stats.find(stat => option.value === stat));
+    const value: Array<SelectOptionItem<string>> = options.filter(option => stats.find(stat => option.value === stat));
 
     return (
       <Select

+ 4 - 1
packages/grafana-ui/src/components/TimePicker/TimePicker.tsx

@@ -231,8 +231,10 @@ export class TimePicker extends PureComponent<Props, State> {
     ];
   };
 
-  onSelectChanged = (item: SelectOptionItem) => {
+  onSelectChanged = (item: SelectOptionItem<TimeOption>) => {
     const { isTimezoneUtc, onChange, timezone } = this.props;
+
+    // @ts-ignore
     onChange(mapTimeOptionToTimeRange(item.value, isTimezoneUtc, timezone));
   };
 
@@ -264,6 +266,7 @@ export class TimePicker extends PureComponent<Props, State> {
     const options = this.mapTimeOptionsToSelectOptionItems(selectTimeOptions);
     const rangeString = mapTimeRangeToRangeString(value);
     const isAbsolute = moment.isMoment(value.raw.to);
+
     return (
       <div className="time-picker">
         <div className="time-picker-buttons">

+ 1 - 0
packages/grafana-ui/src/components/ValueMappingsEditor/MappingRow.tsx

@@ -126,6 +126,7 @@ export default class MappingRow extends PureComponent<Props, State> {
             isSearchable={false}
             options={mappingOptions}
             value={mappingOptions.find(o => o.value === type)}
+            // @ts-ignore
             onChange={type => this.onMappingTypeChange(type.value)}
             width={7}
           />

+ 1 - 1
packages/grafana-ui/src/utils/string.ts

@@ -43,7 +43,7 @@ export function stringToMs(str: string): number {
   }
 }
 
-export function getIntervalFromString(strInterval: string): SelectOptionItem {
+export function getIntervalFromString(strInterval: string): SelectOptionItem<number> {
   return {
     label: strInterval,
     value: stringToMs(strInterval),

+ 1 - 1
public/app/core/components/PermissionList/AddPermission.tsx

@@ -61,7 +61,7 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
     this.setState({ teamId: team && !Array.isArray(team) ? team.id : 0 });
   };
 
-  onPermissionChanged = (permission: SelectOptionItem) => {
+  onPermissionChanged = (permission: SelectOptionItem<PermissionLevel>) => {
     this.setState({ permission: permission.value });
   };
 

+ 1 - 1
public/app/core/components/Select/DataSourcePicker.tsx

@@ -28,7 +28,7 @@ export class DataSourcePicker extends PureComponent<Props> {
     super(props);
   }
 
-  onChange = (item: SelectOptionItem) => {
+  onChange = (item: SelectOptionItem<string>) => {
     const ds = this.props.datasources.find(ds => ds.name === item.value);
     this.props.onChange(ds);
   };

+ 2 - 2
public/app/core/components/Select/MetricSelect.tsx

@@ -6,7 +6,7 @@ import { Variable } from 'app/types/templates';
 
 export interface Props {
   onChange: (value: string) => void;
-  options: SelectOptionItem[];
+  options: Array<SelectOptionItem<string>>;
   isSearchable: boolean;
   value: string;
   placeholder?: string;
@@ -15,7 +15,7 @@ export interface Props {
 }
 
 interface State {
-  options: any[];
+  options: Array<SelectOptionItem<string>>;
 }
 
 export class MetricSelect extends React.Component<Props, State> {

+ 1 - 1
public/app/features/teams/TeamMemberRow.test.tsx

@@ -80,7 +80,7 @@ describe('Functions', () => {
     };
     const { instance } = setup({ member });
     const permission = TeamPermissionLevel.Admin;
-    const item: SelectOptionItem = { value: permission };
+    const item: SelectOptionItem<TeamPermissionLevel> = { value: permission };
     const expectedTeamMemeber = { ...member, permission };
 
     instance.onPermissionChange(item, member);

+ 2 - 2
public/app/features/teams/TeamMemberRow.tsx

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
 import { DeleteButton, Select, SelectOptionItem } from '@grafana/ui';
 
-import { TeamMember, teamsPermissionLevels } from 'app/types';
+import { TeamMember, teamsPermissionLevels, TeamPermissionLevel } from 'app/types';
 import { WithFeatureToggle } from 'app/core/components/WithFeatureToggle';
 import { updateTeamMember, removeTeamMember } from './state/actions';
 import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
@@ -27,7 +27,7 @@ export class TeamMemberRow extends PureComponent<Props> {
     this.props.removeTeamMember(member.userId);
   }
 
-  onPermissionChange = (item: SelectOptionItem, member: TeamMember) => {
+  onPermissionChange = (item: SelectOptionItem<TeamPermissionLevel>, member: TeamMember) => {
     const permission = item.value;
     const updatedTeamMember = { ...member, permission };
 

+ 1 - 1
public/app/plugins/datasource/input/InputQueryEditor.tsx

@@ -29,7 +29,7 @@ export class InputQueryEditor extends PureComponent<Props, State> {
     this.setState({ text });
   }
 
-  onSourceChange = (item: SelectOptionItem) => {
+  onSourceChange = (item: SelectOptionItem<string>) => {
     const { datasource, query, onChange, onRunQuery } = this.props;
     let data: SeriesData[] | undefined = undefined;
     if (item.value === 'panel') {

+ 1 - 1
public/app/plugins/datasource/stackdriver/components/Alignments.tsx

@@ -8,7 +8,7 @@ import { SelectOptionItem } from '@grafana/ui';
 export interface Props {
   onChange: (perSeriesAligner) => void;
   templateSrv: TemplateSrv;
-  alignOptions: SelectOptionItem[];
+  alignOptions: Array<SelectOptionItem<string>>;
   perSeriesAligner: string;
 }
 

+ 1 - 1
public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx

@@ -25,7 +25,7 @@ export interface Props {
 }
 
 interface State extends StackdriverQuery {
-  alignOptions: SelectOptionItem[];
+  alignOptions: Array<SelectOptionItem<string>>;
   lastQuery: string;
   lastQueryError: string;
   [key: string]: any;

+ 1 - 1
public/app/plugins/datasource/testdata/QueryEditor.tsx

@@ -40,7 +40,7 @@ export class QueryEditor extends PureComponent<Props> {
     this.setState({ scenarioList: scenarioList, current: current });
   }
 
-  onScenarioChange = (item: SelectOptionItem) => {
+  onScenarioChange = (item: SelectOptionItem<string>) => {
     this.props.onChange({
       ...this.props.query,
       scenarioId: item.value,

+ 1 - 1
public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx

@@ -46,7 +46,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
     return (
       <>
         <PanelOptionsGrid>
-          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
+          <SingleStatValueEditor onChange={this.onValueOptionsChanged} value={options.valueOptions} />
           <PanelOptionsGroup title="Gauge">
             <FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} />
             <FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={options.maxValue} />

+ 2 - 2
public/app/plugins/panel/bargauge/types.ts

@@ -6,13 +6,13 @@ export interface BarGaugeOptions extends SingleStatBaseOptions {
   displayMode: 'basic' | 'lcd' | 'gradient';
 }
 
-export const displayModes: SelectOptionItem[] = [
+export const displayModes: Array<SelectOptionItem<string>> = [
   { value: 'gradient', label: 'Gradient' },
   { value: 'lcd', label: 'Retro LCD' },
   { value: 'basic', label: 'Basic' },
 ];
 
-export const orientationOptions: SelectOptionItem[] = [
+export const orientationOptions: Array<SelectOptionItem<VizOrientation>> = [
   { value: VizOrientation.Horizontal, label: 'Horizontal' },
   { value: VizOrientation.Vertical, label: 'Vertical' },
 ];

+ 1 - 1
public/app/plugins/panel/gauge/GaugePanelEditor.tsx

@@ -39,7 +39,7 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
     return (
       <>
         <PanelOptionsGrid>
-          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
+          <SingleStatValueEditor onChange={this.onValueOptionsChanged} value={options.valueOptions} />
           <GaugeOptionsBox onOptionsChange={onOptionsChange} options={options} />
           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
         </PanelOptionsGrid>

+ 1 - 1
public/app/plugins/panel/piechart/PieChartPanelEditor.tsx

@@ -30,7 +30,7 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
     return (
       <>
         <PanelOptionsGrid>
-          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
+          <SingleStatValueEditor onChange={this.onValueOptionsChanged} value={options.valueOptions} />
           <PieChartOptionsBox onOptionsChange={onOptionsChange} options={options} />
         </PanelOptionsGrid>
 

+ 5 - 3
public/app/plugins/panel/singlestat2/FontSizeEditor.tsx

@@ -20,11 +20,13 @@ const fontSizeOptions = percents.map(v => {
 });
 
 export class FontSizeEditor extends PureComponent<Props> {
-  setPrefixFontSize = (v: SelectOptionItem) => this.props.onChange({ ...this.props.options, prefixFontSize: v.value });
+  setPrefixFontSize = (v: SelectOptionItem<string>) =>
+    this.props.onChange({ ...this.props.options, prefixFontSize: v.value });
 
-  setValueFontSize = (v: SelectOptionItem) => this.props.onChange({ ...this.props.options, valueFontSize: v.value });
+  setValueFontSize = (v: SelectOptionItem<string>) =>
+    this.props.onChange({ ...this.props.options, valueFontSize: v.value });
 
-  setPostfixFontSize = (v: SelectOptionItem) =>
+  setPostfixFontSize = (v: SelectOptionItem<string>) =>
     this.props.onChange({ ...this.props.options, postfixFontSize: v.value });
 
   render() {

+ 1 - 1
public/app/plugins/panel/singlestat2/SingleStatEditor.tsx

@@ -47,7 +47,7 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
     return (
       <>
         <PanelOptionsGrid>
-          <SingleStatValueEditor onChange={this.onValueOptionsChanged} options={options.valueOptions} />
+          <SingleStatValueEditor onChange={this.onValueOptionsChanged} value={options.valueOptions} />
           <FontSizeEditor options={options} onChange={this.props.onOptionsChange} />
           <ColoringEditor options={options} onChange={this.props.onOptionsChange} />
           <SparklineEditor options={options.sparkline} onChange={this.onSparklineChanged} />

+ 4 - 3
public/app/plugins/panel/text2/TextPanelEditor.tsx

@@ -5,16 +5,17 @@ import React, { PureComponent, ChangeEvent } from 'react';
 import { PanelEditorProps, PanelOptionsGroup, Select, SelectOptionItem } from '@grafana/ui';
 
 // Types
-import { TextOptions } from './types';
+import { TextOptions, TextMode } from './types';
 
 export class TextPanelEditor extends PureComponent<PanelEditorProps<TextOptions>> {
-  modes: SelectOptionItem[] = [
+  modes: Array<SelectOptionItem<TextMode>> = [
     { value: 'markdown', label: 'Markdown' },
     { value: 'text', label: 'Text' },
     { value: 'html', label: 'HTML' },
   ];
 
-  onModeChange = (item: SelectOptionItem) => this.props.onOptionsChange({ ...this.props.options, mode: item.value });
+  onModeChange = (item: SelectOptionItem<TextMode>) =>
+    this.props.onOptionsChange({ ...this.props.options, mode: item.value });
 
   onContentChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {
     this.props.onOptionsChange({ ...this.props.options, content: (event.target as any).value });

+ 2 - 1
public/app/plugins/panel/text2/types.ts

@@ -1,5 +1,6 @@
+export type TextMode = 'html' | 'markdown' | 'text';
 export interface TextOptions {
-  mode: 'html' | 'markdown' | 'text';
+  mode: TextMode;
   content: string;
 }