language_provider.test.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import Plain from 'slate-plain-serializer';
  2. import LanguageProvider from '../language_provider';
  3. describe('Language completion provider', () => {
  4. const datasource = {
  5. metadataRequest: () => ({ data: { data: [] } }),
  6. };
  7. it('returns default suggestions on emtpty context', () => {
  8. const instance = new LanguageProvider(datasource);
  9. const result = instance.provideCompletionItems({ text: '', prefix: '', wrapperClasses: [] });
  10. expect(result.context).toBeUndefined();
  11. expect(result.refresher).toBeUndefined();
  12. expect(result.suggestions.length).toEqual(2);
  13. });
  14. describe('range suggestions', () => {
  15. it('returns range suggestions in range context', () => {
  16. const instance = new LanguageProvider(datasource);
  17. const result = instance.provideCompletionItems({ text: '1', prefix: '1', wrapperClasses: ['context-range'] });
  18. expect(result.context).toBe('context-range');
  19. expect(result.refresher).toBeUndefined();
  20. expect(result.suggestions).toEqual([
  21. {
  22. items: [{ label: '1m' }, { label: '5m' }, { label: '10m' }, { label: '30m' }, { label: '1h' }],
  23. label: 'Range vector',
  24. },
  25. ]);
  26. });
  27. });
  28. describe('metric suggestions', () => {
  29. it('returns metrics suggestions by default', () => {
  30. const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
  31. const result = instance.provideCompletionItems({ text: 'a', prefix: 'a', wrapperClasses: [] });
  32. expect(result.context).toBeUndefined();
  33. expect(result.refresher).toBeUndefined();
  34. expect(result.suggestions.length).toEqual(2);
  35. });
  36. it('returns default suggestions after a binary operator', () => {
  37. const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
  38. const result = instance.provideCompletionItems({ text: '*', prefix: '', wrapperClasses: [] });
  39. expect(result.context).toBeUndefined();
  40. expect(result.refresher).toBeUndefined();
  41. expect(result.suggestions.length).toEqual(2);
  42. });
  43. });
  44. describe('label suggestions', () => {
  45. it('returns default label suggestions on label context and no metric', () => {
  46. const instance = new LanguageProvider(datasource);
  47. const value = Plain.deserialize('{}');
  48. const range = value.selection.merge({
  49. anchorOffset: 1,
  50. });
  51. const valueWithSelection = value.change().select(range).value;
  52. const result = instance.provideCompletionItems({
  53. text: '',
  54. prefix: '',
  55. wrapperClasses: ['context-labels'],
  56. value: valueWithSelection,
  57. });
  58. expect(result.context).toBe('context-labels');
  59. expect(result.suggestions).toEqual([{ items: [{ label: 'job' }, { label: 'instance' }], label: 'Labels' }]);
  60. });
  61. it('returns label suggestions on label context and metric', () => {
  62. const instance = new LanguageProvider(datasource, { labelKeys: { '{__name__="metric"}': ['bar'] } });
  63. const value = Plain.deserialize('metric{}');
  64. const range = value.selection.merge({
  65. anchorOffset: 7,
  66. });
  67. const valueWithSelection = value.change().select(range).value;
  68. const result = instance.provideCompletionItems({
  69. text: '',
  70. prefix: '',
  71. wrapperClasses: ['context-labels'],
  72. value: valueWithSelection,
  73. });
  74. expect(result.context).toBe('context-labels');
  75. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  76. });
  77. it('returns label suggestions on label context but leaves out labels that already exist', () => {
  78. const instance = new LanguageProvider(datasource, {
  79. labelKeys: { '{job1="foo",job2!="foo",job3=~"foo"}': ['bar', 'job1', 'job2', 'job3'] },
  80. });
  81. const value = Plain.deserialize('{job1="foo",job2!="foo",job3=~"foo",}');
  82. const range = value.selection.merge({
  83. anchorOffset: 36,
  84. });
  85. const valueWithSelection = value.change().select(range).value;
  86. const result = instance.provideCompletionItems({
  87. text: '',
  88. prefix: '',
  89. wrapperClasses: ['context-labels'],
  90. value: valueWithSelection,
  91. });
  92. expect(result.context).toBe('context-labels');
  93. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  94. });
  95. it('returns label value suggestions inside a label value context after a negated matching operator', () => {
  96. const instance = new LanguageProvider(datasource, {
  97. labelKeys: { '{}': ['label'] },
  98. labelValues: { '{}': { label: ['a', 'b', 'c'] } },
  99. });
  100. const value = Plain.deserialize('{label!=}');
  101. const range = value.selection.merge({ anchorOffset: 8 });
  102. const valueWithSelection = value.change().select(range).value;
  103. const result = instance.provideCompletionItems({
  104. text: '!=',
  105. prefix: '',
  106. wrapperClasses: ['context-labels'],
  107. labelKey: 'label',
  108. value: valueWithSelection,
  109. });
  110. expect(result.context).toBe('context-label-values');
  111. expect(result.suggestions).toEqual([
  112. {
  113. items: [{ label: 'a' }, { label: 'b' }, { label: 'c' }],
  114. label: 'Label values for "label"',
  115. },
  116. ]);
  117. });
  118. it('returns a refresher on label context and unavailable metric', () => {
  119. const instance = new LanguageProvider(datasource, { labelKeys: { '{__name__="foo"}': ['bar'] } });
  120. const value = Plain.deserialize('metric{}');
  121. const range = value.selection.merge({
  122. anchorOffset: 7,
  123. });
  124. const valueWithSelection = value.change().select(range).value;
  125. const result = instance.provideCompletionItems({
  126. text: '',
  127. prefix: '',
  128. wrapperClasses: ['context-labels'],
  129. value: valueWithSelection,
  130. });
  131. expect(result.context).toBeUndefined();
  132. expect(result.refresher).toBeInstanceOf(Promise);
  133. expect(result.suggestions).toEqual([]);
  134. });
  135. it('returns label values on label context when given a metric and a label key', () => {
  136. const instance = new LanguageProvider(datasource, {
  137. labelKeys: { '{__name__="metric"}': ['bar'] },
  138. labelValues: { '{__name__="metric"}': { bar: ['baz'] } },
  139. });
  140. const value = Plain.deserialize('metric{bar=ba}');
  141. const range = value.selection.merge({
  142. anchorOffset: 13,
  143. });
  144. const valueWithSelection = value.change().select(range).value;
  145. const result = instance.provideCompletionItems({
  146. text: '=ba',
  147. prefix: 'ba',
  148. wrapperClasses: ['context-labels'],
  149. labelKey: 'bar',
  150. value: valueWithSelection,
  151. });
  152. expect(result.context).toBe('context-label-values');
  153. expect(result.suggestions).toEqual([{ items: [{ label: 'baz' }], label: 'Label values for "bar"' }]);
  154. });
  155. it('returns label suggestions on aggregation context and metric w/ selector', () => {
  156. const instance = new LanguageProvider(datasource, { labelKeys: { '{__name__="metric",foo="xx"}': ['bar'] } });
  157. const value = Plain.deserialize('sum(metric{foo="xx"}) by ()');
  158. const range = value.selection.merge({
  159. anchorOffset: 26,
  160. });
  161. const valueWithSelection = value.change().select(range).value;
  162. const result = instance.provideCompletionItems({
  163. text: '',
  164. prefix: '',
  165. wrapperClasses: ['context-aggregation'],
  166. value: valueWithSelection,
  167. });
  168. expect(result.context).toBe('context-aggregation');
  169. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  170. });
  171. it('returns label suggestions on aggregation context and metric w/o selector', () => {
  172. const instance = new LanguageProvider(datasource, { labelKeys: { '{__name__="metric"}': ['bar'] } });
  173. const value = Plain.deserialize('sum(metric) by ()');
  174. const range = value.selection.merge({
  175. anchorOffset: 16,
  176. });
  177. const valueWithSelection = value.change().select(range).value;
  178. const result = instance.provideCompletionItems({
  179. text: '',
  180. prefix: '',
  181. wrapperClasses: ['context-aggregation'],
  182. value: valueWithSelection,
  183. });
  184. expect(result.context).toBe('context-aggregation');
  185. expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]);
  186. });
  187. it('returns label suggestions inside a multi-line aggregation context', () => {
  188. const instance = new LanguageProvider(datasource, {
  189. labelKeys: { '{__name__="metric"}': ['label1', 'label2', 'label3'] },
  190. });
  191. const value = Plain.deserialize('sum(\nmetric\n)\nby ()');
  192. const aggregationTextBlock = value.document.getBlocksAsArray()[3];
  193. const range = value.selection.moveToStartOf(aggregationTextBlock).merge({ anchorOffset: 4 });
  194. const valueWithSelection = value.change().select(range).value;
  195. const result = instance.provideCompletionItems({
  196. text: '',
  197. prefix: '',
  198. wrapperClasses: ['context-aggregation'],
  199. value: valueWithSelection,
  200. });
  201. expect(result.context).toBe('context-aggregation');
  202. expect(result.suggestions).toEqual([
  203. {
  204. items: [{ label: 'label1' }, { label: 'label2' }, { label: 'label3' }],
  205. label: 'Labels',
  206. },
  207. ]);
  208. });
  209. it('returns label suggestions inside an aggregation context with a range vector', () => {
  210. const instance = new LanguageProvider(datasource, {
  211. labelKeys: { '{__name__="metric"}': ['label1', 'label2', 'label3'] },
  212. });
  213. const value = Plain.deserialize('sum(rate(metric[1h])) by ()');
  214. const range = value.selection.merge({
  215. anchorOffset: 26,
  216. });
  217. const valueWithSelection = value.change().select(range).value;
  218. const result = instance.provideCompletionItems({
  219. text: '',
  220. prefix: '',
  221. wrapperClasses: ['context-aggregation'],
  222. value: valueWithSelection,
  223. });
  224. expect(result.context).toBe('context-aggregation');
  225. expect(result.suggestions).toEqual([
  226. {
  227. items: [{ label: 'label1' }, { label: 'label2' }, { label: 'label3' }],
  228. label: 'Labels',
  229. },
  230. ]);
  231. });
  232. it('returns label suggestions inside an aggregation context with a range vector and label', () => {
  233. const instance = new LanguageProvider(datasource, {
  234. labelKeys: { '{__name__="metric",label1="value"}': ['label1', 'label2', 'label3'] },
  235. });
  236. const value = Plain.deserialize('sum(rate(metric{label1="value"}[1h])) by ()');
  237. const range = value.selection.merge({
  238. anchorOffset: 42,
  239. });
  240. const valueWithSelection = value.change().select(range).value;
  241. const result = instance.provideCompletionItems({
  242. text: '',
  243. prefix: '',
  244. wrapperClasses: ['context-aggregation'],
  245. value: valueWithSelection,
  246. });
  247. expect(result.context).toBe('context-aggregation');
  248. expect(result.suggestions).toEqual([
  249. {
  250. items: [{ label: 'label1' }, { label: 'label2' }, { label: 'label3' }],
  251. label: 'Labels',
  252. },
  253. ]);
  254. });
  255. it('returns no suggestions inside an unclear aggregation context using alternate syntax', () => {
  256. const instance = new LanguageProvider(datasource, {
  257. labelKeys: { '{__name__="metric"}': ['label1', 'label2', 'label3'] },
  258. });
  259. const value = Plain.deserialize('sum by ()');
  260. const range = value.selection.merge({
  261. anchorOffset: 8,
  262. });
  263. const valueWithSelection = value.change().select(range).value;
  264. const result = instance.provideCompletionItems({
  265. text: '',
  266. prefix: '',
  267. wrapperClasses: ['context-aggregation'],
  268. value: valueWithSelection,
  269. });
  270. expect(result.context).toBe('context-aggregation');
  271. expect(result.suggestions).toEqual([]);
  272. });
  273. it('returns label suggestions inside an aggregation context using alternate syntax', () => {
  274. const instance = new LanguageProvider(datasource, {
  275. labelKeys: { '{__name__="metric"}': ['label1', 'label2', 'label3'] },
  276. });
  277. const value = Plain.deserialize('sum by () (metric)');
  278. const range = value.selection.merge({
  279. anchorOffset: 8,
  280. });
  281. const valueWithSelection = value.change().select(range).value;
  282. const result = instance.provideCompletionItems({
  283. text: '',
  284. prefix: '',
  285. wrapperClasses: ['context-aggregation'],
  286. value: valueWithSelection,
  287. });
  288. expect(result.context).toBe('context-aggregation');
  289. expect(result.suggestions).toEqual([
  290. {
  291. items: [{ label: 'label1' }, { label: 'label2' }, { label: 'label3' }],
  292. label: 'Labels',
  293. },
  294. ]);
  295. });
  296. });
  297. });