OptionsPicker.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import React, { ComponentType, PureComponent } from 'react';
  2. import { connect, ConnectedProps } from 'react-redux';
  3. import { bindActionCreators } from 'redux';
  4. import { LoadingState } from '@grafana/data';
  5. import { ClickOutsideWrapper } from '@grafana/ui';
  6. import { StoreState, ThunkDispatch } from 'app/types';
  7. import { isMulti } from '../../guard';
  8. import { getVariableQueryRunner } from '../../query/VariableQueryRunner';
  9. import { formatVariableLabel } from '../../shared/formatVariable';
  10. import { toKeyedAction } from '../../state/keyedVariablesReducer';
  11. import { getVariablesState } from '../../state/selectors';
  12. import { KeyedVariableIdentifier } from '../../state/types';
  13. import { VariableOption, VariableWithMultiSupport, VariableWithOptions } from '../../types';
  14. import { toKeyedVariableIdentifier } from '../../utils';
  15. import { VariableInput } from '../shared/VariableInput';
  16. import { VariableLink } from '../shared/VariableLink';
  17. import { VariableOptions } from '../shared/VariableOptions';
  18. import { NavigationKey, VariablePickerProps } from '../types';
  19. import { commitChangesToVariable, filterOrSearchOptions, navigateOptions, openOptions } from './actions';
  20. import { initialOptionPickerState, OptionsPickerState, toggleAllOptions, toggleOption } from './reducer';
  21. export const optionPickerFactory = <Model extends VariableWithOptions | VariableWithMultiSupport>(): ComponentType<
  22. VariablePickerProps<Model>
  23. > => {
  24. const mapDispatchToProps = (dispatch: ThunkDispatch) => {
  25. return {
  26. ...bindActionCreators({ openOptions, commitChangesToVariable, navigateOptions }, dispatch),
  27. filterOrSearchOptions: (identifier: KeyedVariableIdentifier, filter = '') => {
  28. dispatch(filterOrSearchOptions(identifier, filter));
  29. },
  30. toggleAllOptions: (identifier: KeyedVariableIdentifier) =>
  31. dispatch(toKeyedAction(identifier.rootStateKey, toggleAllOptions())),
  32. toggleOption: (
  33. identifier: KeyedVariableIdentifier,
  34. option: VariableOption,
  35. clearOthers: boolean,
  36. forceSelect: boolean
  37. ) => dispatch(toKeyedAction(identifier.rootStateKey, toggleOption({ option, clearOthers, forceSelect }))),
  38. };
  39. };
  40. const mapStateToProps = (state: StoreState, ownProps: OwnProps) => {
  41. const { rootStateKey } = ownProps.variable;
  42. if (!rootStateKey) {
  43. console.error('OptionPickerFactory: variable has no rootStateKey');
  44. return {
  45. picker: initialOptionPickerState,
  46. };
  47. }
  48. return {
  49. picker: getVariablesState(rootStateKey, state).optionsPicker,
  50. };
  51. };
  52. const connector = connect(mapStateToProps, mapDispatchToProps);
  53. interface OwnProps extends VariablePickerProps<Model> {}
  54. type Props = OwnProps & ConnectedProps<typeof connector>;
  55. class OptionsPickerUnconnected extends PureComponent<Props> {
  56. onShowOptions = () =>
  57. this.props.openOptions(toKeyedVariableIdentifier(this.props.variable), this.props.onVariableChange);
  58. onHideOptions = () => {
  59. if (!this.props.variable.rootStateKey) {
  60. console.error('Variable has no rootStateKey');
  61. return;
  62. }
  63. this.props.commitChangesToVariable(this.props.variable.rootStateKey, this.props.onVariableChange);
  64. };
  65. onToggleOption = (option: VariableOption, clearOthers: boolean) => {
  66. const toggleFunc =
  67. isMulti(this.props.variable) && this.props.variable.multi
  68. ? this.onToggleMultiValueVariable
  69. : this.onToggleSingleValueVariable;
  70. toggleFunc(option, clearOthers);
  71. };
  72. onToggleSingleValueVariable = (option: VariableOption, clearOthers: boolean) => {
  73. this.props.toggleOption(toKeyedVariableIdentifier(this.props.variable), option, clearOthers, false);
  74. this.onHideOptions();
  75. };
  76. onToggleMultiValueVariable = (option: VariableOption, clearOthers: boolean) => {
  77. this.props.toggleOption(toKeyedVariableIdentifier(this.props.variable), option, clearOthers, false);
  78. };
  79. onToggleAllOptions = () => {
  80. this.props.toggleAllOptions(toKeyedVariableIdentifier(this.props.variable));
  81. };
  82. onFilterOrSearchOptions = (filter: string) => {
  83. this.props.filterOrSearchOptions(toKeyedVariableIdentifier(this.props.variable), filter);
  84. };
  85. onNavigate = (key: NavigationKey, clearOthers: boolean) => {
  86. if (!this.props.variable.rootStateKey) {
  87. console.error('Variable has no rootStateKey');
  88. return;
  89. }
  90. this.props.navigateOptions(this.props.variable.rootStateKey, key, clearOthers);
  91. };
  92. render() {
  93. const { variable, picker } = this.props;
  94. const showOptions = picker.id === variable.id;
  95. return (
  96. <div className="variable-link-wrapper">
  97. {showOptions ? this.renderOptions(picker) : this.renderLink(variable)}
  98. </div>
  99. );
  100. }
  101. renderLink(variable: VariableWithOptions) {
  102. const linkText = formatVariableLabel(variable);
  103. const loading = variable.state === LoadingState.Loading;
  104. return (
  105. <VariableLink
  106. id={`var-${variable.id}`}
  107. text={linkText}
  108. onClick={this.onShowOptions}
  109. loading={loading}
  110. onCancel={this.onCancel}
  111. />
  112. );
  113. }
  114. onCancel = () => {
  115. getVariableQueryRunner().cancelRequest(toKeyedVariableIdentifier(this.props.variable));
  116. };
  117. renderOptions(picker: OptionsPickerState) {
  118. const { id } = this.props.variable;
  119. return (
  120. <ClickOutsideWrapper onClick={this.onHideOptions}>
  121. <VariableInput
  122. id={`var-${id}`}
  123. value={picker.queryValue}
  124. onChange={this.onFilterOrSearchOptions}
  125. onNavigate={this.onNavigate}
  126. aria-expanded={true}
  127. aria-controls={`options-${id}`}
  128. />
  129. <VariableOptions
  130. values={picker.options}
  131. onToggle={this.onToggleOption}
  132. onToggleAll={this.onToggleAllOptions}
  133. highlightIndex={picker.highlightIndex}
  134. multi={picker.multi}
  135. selectedValues={picker.selectedValues}
  136. id={`options-${id}`}
  137. />
  138. </ClickOutsideWrapper>
  139. );
  140. }
  141. }
  142. const OptionsPicker = connector(OptionsPickerUnconnected);
  143. OptionsPicker.displayName = 'OptionsPicker';
  144. return OptionsPicker;
  145. };