OperationHeader.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { css } from '@emotion/css';
  2. import React, { useState } from 'react';
  3. import { GrafanaTheme2, SelectableValue } from '@grafana/data';
  4. import { FlexItem } from '@grafana/experimental';
  5. import { Button, Select, useStyles2 } from '@grafana/ui';
  6. import { OperationInfoButton } from './OperationInfoButton';
  7. import { VisualQueryModeller, QueryBuilderOperation, QueryBuilderOperationDef } from './types';
  8. export interface Props {
  9. operation: QueryBuilderOperation;
  10. def: QueryBuilderOperationDef;
  11. index: number;
  12. queryModeller: VisualQueryModeller;
  13. dragHandleProps: any;
  14. onChange: (index: number, update: QueryBuilderOperation) => void;
  15. onRemove: (index: number) => void;
  16. }
  17. interface State {
  18. isOpen?: boolean;
  19. alternatives?: Array<SelectableValue<QueryBuilderOperationDef>>;
  20. }
  21. export const OperationHeader = React.memo<Props>(
  22. ({ operation, def, index, onChange, onRemove, queryModeller, dragHandleProps }) => {
  23. const styles = useStyles2(getStyles);
  24. const [state, setState] = useState<State>({});
  25. const onToggleSwitcher = () => {
  26. if (state.isOpen) {
  27. setState({ ...state, isOpen: false });
  28. } else {
  29. const alternatives = queryModeller
  30. .getAlternativeOperations(def.alternativesKey!)
  31. .map((alt) => ({ label: alt.name, value: alt }));
  32. setState({ isOpen: true, alternatives });
  33. }
  34. };
  35. return (
  36. <div className={styles.header}>
  37. {!state.isOpen && (
  38. <>
  39. <div {...dragHandleProps}>{def.name ?? def.id}</div>
  40. <FlexItem grow={1} />
  41. <div className={`${styles.operationHeaderButtons} operation-header-show-on-hover`}>
  42. <Button
  43. icon="angle-down"
  44. size="sm"
  45. onClick={onToggleSwitcher}
  46. fill="text"
  47. variant="secondary"
  48. title="Click to view alternative operations"
  49. />
  50. <OperationInfoButton def={def} operation={operation} />
  51. <Button
  52. icon="times"
  53. size="sm"
  54. onClick={() => onRemove(index)}
  55. fill="text"
  56. variant="secondary"
  57. title="Remove operation"
  58. />
  59. </div>
  60. </>
  61. )}
  62. {state.isOpen && (
  63. <div className={styles.selectWrapper}>
  64. <Select
  65. autoFocus
  66. openMenuOnFocus
  67. placeholder="Replace with"
  68. options={state.alternatives}
  69. isOpen={true}
  70. onCloseMenu={onToggleSwitcher}
  71. onChange={(value) => {
  72. if (value.value) {
  73. // Operation should exist if it is selectable
  74. const newDef = queryModeller.getOperationDef(value.value.id)!;
  75. // copy default params, and override with all current params
  76. const newParams = [...newDef.defaultParams];
  77. for (let i = 0; i < Math.min(operation.params.length, newParams.length); i++) {
  78. if (newDef.params[i].type === def.params[i].type) {
  79. newParams[i] = operation.params[i];
  80. }
  81. }
  82. const changedOp = { ...operation, params: newParams, id: value.value.id };
  83. onChange(index, def.changeTypeHandler ? def.changeTypeHandler(changedOp, newDef) : changedOp);
  84. }
  85. }}
  86. />
  87. </div>
  88. )}
  89. </div>
  90. );
  91. }
  92. );
  93. OperationHeader.displayName = 'OperationHeader';
  94. const getStyles = (theme: GrafanaTheme2) => {
  95. return {
  96. header: css({
  97. borderBottom: `1px solid ${theme.colors.border.medium}`,
  98. padding: theme.spacing(0.5, 0.5, 0.5, 1),
  99. display: 'flex',
  100. alignItems: 'center',
  101. '&:hover .operation-header-show-on-hover': css({
  102. opacity: 1,
  103. }),
  104. }),
  105. operationHeaderButtons: css({
  106. opacity: 0,
  107. transition: theme.transitions.create(['opacity'], {
  108. duration: theme.transitions.duration.short,
  109. }),
  110. }),
  111. selectWrapper: css({
  112. paddingRight: theme.spacing(2),
  113. }),
  114. };
  115. };