PromQueryField.test.tsx 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import React from 'react';
  2. import Enzyme, { shallow } from 'enzyme';
  3. import Adapter from 'enzyme-adapter-react-16';
  4. import Plain from 'slate-plain-serializer';
  5. import PromQueryField, { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField';
  6. Enzyme.configure({ adapter: new Adapter() });
  7. describe('PromQueryField typeahead handling', () => {
  8. const defaultProps = {
  9. request: () => ({ data: { data: [] } }),
  10. };
  11. it('returns default suggestions on emtpty context', () => {
  12. const instance = shallow(<PromQueryField {...defaultProps} />).instance() as PromQueryField;
  13. const result = instance.getTypeahead({ text: '', prefix: '', wrapperClasses: [] });
  14. expect(result.context).toBeUndefined();
  15. expect(result.refresher).toBeUndefined();
  16. expect(result.suggestions.length).toEqual(2);
  17. });
  18. describe('range suggestions', () => {
  19. it('returns range suggestions in range context', () => {
  20. const instance = shallow(<PromQueryField {...defaultProps} />).instance() as PromQueryField;
  21. const result = instance.getTypeahead({ text: '1', prefix: '1', wrapperClasses: ['context-range'] });
  22. expect(result.context).toBe('context-range');
  23. expect(result.refresher).toBeUndefined();
  24. expect(result.suggestions).toEqual([
  25. {
  26. items: [{ label: '1m' }, { label: '5m' }, { label: '10m' }, { label: '30m' }, { label: '1h' }],
  27. label: 'Range vector',
  28. },
  29. ]);
  30. });
  31. });
  32. describe('metric suggestions', () => {
  33. it('returns metrics suggestions by default', () => {
  34. const instance = shallow(
  35. <PromQueryField {...defaultProps} metrics={['foo', 'bar']} />
  36. ).instance() as PromQueryField;
  37. const result = instance.getTypeahead({ text: 'a', prefix: 'a', wrapperClasses: [] });
  38. expect(result.context).toBeUndefined();
  39. expect(result.refresher).toBeUndefined();
  40. expect(result.suggestions.length).toEqual(2);
  41. });
  42. it('returns default suggestions after a binary operator', () => {
  43. const instance = shallow(
  44. <PromQueryField {...defaultProps} metrics={['foo', 'bar']} />
  45. ).instance() as PromQueryField;
  46. const result = instance.getTypeahead({ text: '*', prefix: '', wrapperClasses: [] });
  47. expect(result.context).toBeUndefined();
  48. expect(result.refresher).toBeUndefined();
  49. expect(result.suggestions.length).toEqual(2);
  50. });
  51. });
  52. describe('label suggestions', () => {
  53. it('returns default label suggestions on label context and no metric', () => {
  54. const instance = shallow(<PromQueryField {...defaultProps} />).instance() as PromQueryField;
  55. const value = Plain.deserialize('{}');
  56. const range = value.selection.merge({
  57. anchorOffset: 1,
  58. });
  59. const valueWithSelection = value.change().select(range).value;
  60. const result = instance.getTypeahead({
  61. text: '',
  62. prefix: '',
  63. wrapperClasses: ['context-labels'],
  64. value: valueWithSelection,
  65. });
  66. expect(result.context).toBe('context-labels');
  67. expect(result.suggestions).toEqual([{ items: [{ label: 'job' }, { label: 'instance' }], label: 'Labels' }]);
  68. });
  69. it('returns label suggestions on label context and metric', () => {
  70. const instance = shallow(
  71. <PromQueryField {...defaultProps} labelKeys={{ '{__name__="metric"}': ['bar'] }} />
  72. ).instance() as PromQueryField;
  73. const value = Plain.deserialize('metric{}');
  74. const range = value.selection.merge({
  75. anchorOffset: 7,
  76. });
  77. const valueWithSelection = value.change().select(range).value;
  78. const result = instance.getTypeahead({
  79. text: '',
  80. prefix: '',
  81. wrapperClasses: ['context-labels'],
  82. value: valueWithSelection,
  83. });
  84. expect(result.context).toBe('context-labels');
  85. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  86. });
  87. it('returns label suggestions on label context but leaves out labels that already exist', () => {
  88. const instance = shallow(
  89. <PromQueryField
  90. {...defaultProps}
  91. labelKeys={{ '{job1="foo",job2!="foo",job3=~"foo"}': ['bar', 'job1', 'job2', 'job3'] }}
  92. />
  93. ).instance() as PromQueryField;
  94. const value = Plain.deserialize('{job1="foo",job2!="foo",job3=~"foo",}');
  95. const range = value.selection.merge({
  96. anchorOffset: 36,
  97. });
  98. const valueWithSelection = value.change().select(range).value;
  99. const result = instance.getTypeahead({
  100. text: '',
  101. prefix: '',
  102. wrapperClasses: ['context-labels'],
  103. value: valueWithSelection,
  104. });
  105. expect(result.context).toBe('context-labels');
  106. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  107. });
  108. it('returns label value suggestions inside a label value context after a negated matching operator', () => {
  109. const instance = shallow(
  110. <PromQueryField
  111. {...defaultProps}
  112. labelKeys={{ '{}': ['label'] }}
  113. labelValues={{ '{}': { label: ['a', 'b', 'c'] } }}
  114. />
  115. ).instance() as PromQueryField;
  116. const value = Plain.deserialize('{label!=}');
  117. const range = value.selection.merge({ anchorOffset: 8 });
  118. const valueWithSelection = value.change().select(range).value;
  119. const result = instance.getTypeahead({
  120. text: '!=',
  121. prefix: '',
  122. wrapperClasses: ['context-labels'],
  123. labelKey: 'label',
  124. value: valueWithSelection,
  125. });
  126. expect(result.context).toBe('context-label-values');
  127. expect(result.suggestions).toEqual([
  128. {
  129. items: [{ label: 'a' }, { label: 'b' }, { label: 'c' }],
  130. label: 'Label values for "label"',
  131. },
  132. ]);
  133. });
  134. it('returns a refresher on label context and unavailable metric', () => {
  135. const instance = shallow(
  136. <PromQueryField {...defaultProps} labelKeys={{ '{__name__="foo"}': ['bar'] }} />
  137. ).instance() as PromQueryField;
  138. const value = Plain.deserialize('metric{}');
  139. const range = value.selection.merge({
  140. anchorOffset: 7,
  141. });
  142. const valueWithSelection = value.change().select(range).value;
  143. const result = instance.getTypeahead({
  144. text: '',
  145. prefix: '',
  146. wrapperClasses: ['context-labels'],
  147. value: valueWithSelection,
  148. });
  149. expect(result.context).toBeUndefined();
  150. expect(result.refresher).toBeInstanceOf(Promise);
  151. expect(result.suggestions).toEqual([]);
  152. });
  153. it('returns label values on label context when given a metric and a label key', () => {
  154. const instance = shallow(
  155. <PromQueryField
  156. {...defaultProps}
  157. labelKeys={{ '{__name__="metric"}': ['bar'] }}
  158. labelValues={{ '{__name__="metric"}': { bar: ['baz'] } }}
  159. />
  160. ).instance() as PromQueryField;
  161. const value = Plain.deserialize('metric{bar=ba}');
  162. const range = value.selection.merge({
  163. anchorOffset: 13,
  164. });
  165. const valueWithSelection = value.change().select(range).value;
  166. const result = instance.getTypeahead({
  167. text: '=ba',
  168. prefix: 'ba',
  169. wrapperClasses: ['context-labels'],
  170. labelKey: 'bar',
  171. value: valueWithSelection,
  172. });
  173. expect(result.context).toBe('context-label-values');
  174. expect(result.suggestions).toEqual([{ items: [{ label: 'baz' }], label: 'Label values for "bar"' }]);
  175. });
  176. it('returns label suggestions on aggregation context and metric w/ selector', () => {
  177. const instance = shallow(
  178. <PromQueryField {...defaultProps} labelKeys={{ '{__name__="metric",foo="xx"}': ['bar'] }} />
  179. ).instance() as PromQueryField;
  180. const value = Plain.deserialize('sum(metric{foo="xx"}) by ()');
  181. const range = value.selection.merge({
  182. anchorOffset: 26,
  183. });
  184. const valueWithSelection = value.change().select(range).value;
  185. const result = instance.getTypeahead({
  186. text: '',
  187. prefix: '',
  188. wrapperClasses: ['context-aggregation'],
  189. value: valueWithSelection,
  190. });
  191. expect(result.context).toBe('context-aggregation');
  192. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  193. });
  194. it('returns label suggestions on aggregation context and metric w/o selector', () => {
  195. const instance = shallow(
  196. <PromQueryField {...defaultProps} labelKeys={{ '{__name__="metric"}': ['bar'] }} />
  197. ).instance() as PromQueryField;
  198. const value = Plain.deserialize('sum(metric) by ()');
  199. const range = value.selection.merge({
  200. anchorOffset: 16,
  201. });
  202. const valueWithSelection = value.change().select(range).value;
  203. const result = instance.getTypeahead({
  204. text: '',
  205. prefix: '',
  206. wrapperClasses: ['context-aggregation'],
  207. value: valueWithSelection,
  208. });
  209. expect(result.context).toBe('context-aggregation');
  210. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  211. });
  212. });
  213. });
  214. describe('groupMetricsByPrefix()', () => {
  215. it('returns an empty group for no metrics', () => {
  216. expect(groupMetricsByPrefix([])).toEqual([]);
  217. });
  218. it('returns options grouped by prefix', () => {
  219. expect(groupMetricsByPrefix(['foo_metric'])).toMatchObject([
  220. {
  221. value: 'foo',
  222. children: [
  223. {
  224. value: 'foo_metric',
  225. },
  226. ],
  227. },
  228. ]);
  229. });
  230. it('returns options without prefix as toplevel option', () => {
  231. expect(groupMetricsByPrefix(['metric'])).toMatchObject([
  232. {
  233. value: 'metric',
  234. },
  235. ]);
  236. });
  237. it('returns recording rules grouped separately', () => {
  238. expect(groupMetricsByPrefix([':foo_metric:'])).toMatchObject([
  239. {
  240. value: RECORDING_RULES_GROUP,
  241. children: [
  242. {
  243. value: ':foo_metric:',
  244. },
  245. ],
  246. },
  247. ]);
  248. });
  249. });