query_ctrl.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. import { StackdriverQueryCtrl } from '../query_ctrl';
  2. import { TemplateSrvStub } from 'test/specs/helpers';
  3. describe('StackdriverQueryCtrl', () => {
  4. let ctrl;
  5. let result;
  6. describe('when initializing query editor', () => {
  7. beforeEach(() => {
  8. const existingFilters = ['key1', '=', 'val1', 'AND', 'key2', '=', 'val2'];
  9. ctrl = createCtrlWithFakes(existingFilters);
  10. });
  11. it('should initialize filter segments using the target filter values', () => {
  12. expect(ctrl.filterSegments.length).toBe(8);
  13. expect(ctrl.filterSegments[0].type).toBe('key');
  14. expect(ctrl.filterSegments[1].type).toBe('operator');
  15. expect(ctrl.filterSegments[2].type).toBe('value');
  16. expect(ctrl.filterSegments[3].type).toBe('condition');
  17. expect(ctrl.filterSegments[4].type).toBe('key');
  18. expect(ctrl.filterSegments[5].type).toBe('operator');
  19. expect(ctrl.filterSegments[6].type).toBe('value');
  20. expect(ctrl.filterSegments[7].type).toBe('plus-button');
  21. });
  22. });
  23. describe('group bys', () => {
  24. beforeEach(() => {
  25. ctrl = createCtrlWithFakes();
  26. });
  27. describe('when labels are fetched', () => {
  28. beforeEach(async () => {
  29. ctrl.metricLabels = { 'metric-key-1': ['metric-value-1'] };
  30. ctrl.resourceLabels = { 'resource-key-1': ['resource-value-1'] };
  31. result = await ctrl.getGroupBys();
  32. });
  33. it('should populate group bys segments', () => {
  34. expect(result.length).toBe(3);
  35. expect(result[0].value).toBe('metric.label.metric-key-1');
  36. expect(result[1].value).toBe('resource.label.resource-key-1');
  37. expect(result[2].value).toBe('-- remove group by --');
  38. });
  39. });
  40. describe('when a group by label is selected', () => {
  41. beforeEach(async () => {
  42. ctrl.metricLabels = {
  43. 'metric-key-1': ['metric-value-1'],
  44. 'metric-key-2': ['metric-value-2'],
  45. };
  46. ctrl.resourceLabels = {
  47. 'resource-key-1': ['resource-value-1'],
  48. 'resource-key-2': ['resource-value-2'],
  49. };
  50. ctrl.target.aggregation.groupBys = ['metric.label.metric-key-1', 'resource.label.resource-key-1'];
  51. result = await ctrl.getGroupBys();
  52. });
  53. it('should not be used to populate group bys segments', () => {
  54. expect(result.length).toBe(3);
  55. expect(result[0].value).toBe('metric.label.metric-key-2');
  56. expect(result[1].value).toBe('resource.label.resource-key-2');
  57. expect(result[2].value).toBe('-- remove group by --');
  58. });
  59. });
  60. describe('when a group by is selected', () => {
  61. beforeEach(() => {
  62. const removeSegment = { fake: true, value: '-- remove group by --' };
  63. const segment = { value: 'groupby1' };
  64. ctrl.groupBySegments = [segment, removeSegment];
  65. ctrl.groupByChanged(segment);
  66. });
  67. it('should be added to group bys list', () => {
  68. expect(ctrl.target.aggregation.groupBys.length).toBe(1);
  69. });
  70. });
  71. describe('when a selected group by is removed', () => {
  72. beforeEach(() => {
  73. const removeSegment = { fake: true, value: '-- remove group by --' };
  74. const segment = { value: 'groupby1' };
  75. ctrl.groupBySegments = [segment, removeSegment];
  76. ctrl.groupByChanged(removeSegment);
  77. });
  78. it('should be added to group bys list', () => {
  79. expect(ctrl.target.aggregation.groupBys.length).toBe(0);
  80. });
  81. });
  82. });
  83. describe('filters', () => {
  84. beforeEach(() => {
  85. ctrl = createCtrlWithFakes();
  86. });
  87. describe('when values for a condition filter part are fetched', () => {
  88. beforeEach(async () => {
  89. const segment = { type: 'condition' };
  90. result = await ctrl.getFilters(segment, 0);
  91. });
  92. it('should populate group bys segments', () => {
  93. expect(result.length).toBe(1);
  94. expect(result[0].value).toBe('AND');
  95. });
  96. });
  97. describe('when values for a operator filter part are fetched', () => {
  98. beforeEach(async () => {
  99. const segment = { type: 'operator' };
  100. result = await ctrl.getFilters(segment, 0);
  101. });
  102. it('should populate group bys segments', () => {
  103. expect(result.length).toBe(4);
  104. expect(result[0].value).toBe('=');
  105. expect(result[1].value).toBe('!=');
  106. expect(result[2].value).toBe('=~');
  107. expect(result[3].value).toBe('!=~');
  108. });
  109. });
  110. describe('when values for a key filter part are fetched', () => {
  111. beforeEach(async () => {
  112. ctrl.metricLabels = {
  113. 'metric-key-1': ['metric-value-1'],
  114. 'metric-key-2': ['metric-value-2'],
  115. };
  116. ctrl.resourceLabels = {
  117. 'resource-key-1': ['resource-value-1'],
  118. 'resource-key-2': ['resource-value-2'],
  119. };
  120. const segment = { type: 'key' };
  121. result = await ctrl.getFilters(segment, 0);
  122. });
  123. it('should populate group bys segments', () => {
  124. expect(result.length).toBe(5);
  125. expect(result[0].value).toBe('metric.label.metric-key-1');
  126. expect(result[1].value).toBe('metric.label.metric-key-2');
  127. expect(result[2].value).toBe('resource.label.resource-key-1');
  128. expect(result[3].value).toBe('resource.label.resource-key-2');
  129. expect(result[4].value).toBe('-- remove filter --');
  130. });
  131. });
  132. describe('when values for a value filter part are fetched', () => {
  133. beforeEach(async () => {
  134. ctrl.metricLabels = {
  135. 'metric-key-1': ['metric-value-1'],
  136. 'metric-key-2': ['metric-value-2'],
  137. };
  138. ctrl.resourceLabels = {
  139. 'resource-key-1': ['resource-value-1'],
  140. 'resource-key-2': ['resource-value-2'],
  141. };
  142. ctrl.filterSegments = [{ type: 'key', value: 'metric.label.metric-key-1' }, { type: 'operator', value: '=' }];
  143. const segment = { type: 'value' };
  144. result = await ctrl.getFilters(segment, 2);
  145. });
  146. it('should populate group bys segments', () => {
  147. expect(result.length).toBe(1);
  148. expect(result[0].value).toBe('metric-value-1');
  149. });
  150. });
  151. describe('when a filter is created by clicking on plus button', () => {
  152. describe('and there are no other filters', () => {
  153. beforeEach(() => {
  154. const segment = { value: 'filterkey1', type: 'plus-button' };
  155. ctrl.filterSegments = [segment];
  156. ctrl.filterSegmentUpdated(segment, 0);
  157. });
  158. it('should transform the plus button segment to a key segment', () => {
  159. expect(ctrl.filterSegments[0].type).toBe('key');
  160. });
  161. it('should add an operator, value segment and plus button segment', () => {
  162. expect(ctrl.filterSegments.length).toBe(3);
  163. expect(ctrl.filterSegments[1].type).toBe('operator');
  164. expect(ctrl.filterSegments[2].type).toBe('value');
  165. });
  166. });
  167. });
  168. describe('when has one existing filter', () => {
  169. describe('and user clicks on key segment', () => {
  170. beforeEach(() => {
  171. const existingKeySegment = { value: 'filterkey1', type: 'key' };
  172. const existingOperatorSegment = { value: '=', type: 'operator' };
  173. const existingValueSegment = { value: 'filtervalue', type: 'value' };
  174. const plusSegment = { value: '', type: 'plus-button' };
  175. ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment, plusSegment];
  176. ctrl.filterSegmentUpdated(existingKeySegment, 0);
  177. });
  178. it('should not add any new segments', () => {
  179. expect(ctrl.filterSegments.length).toBe(4);
  180. expect(ctrl.filterSegments[0].type).toBe('key');
  181. expect(ctrl.filterSegments[1].type).toBe('operator');
  182. expect(ctrl.filterSegments[2].type).toBe('value');
  183. });
  184. });
  185. describe('and user clicks on value segment and value not equal to fake value', () => {
  186. beforeEach(() => {
  187. const existingKeySegment = { value: 'filterkey1', type: 'key' };
  188. const existingOperatorSegment = { value: '=', type: 'operator' };
  189. const existingValueSegment = { value: 'filtervalue', type: 'value' };
  190. ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment];
  191. ctrl.filterSegmentUpdated(existingValueSegment, 2);
  192. });
  193. it('should ensure that plus segment exists', () => {
  194. expect(ctrl.filterSegments.length).toBe(4);
  195. expect(ctrl.filterSegments[0].type).toBe('key');
  196. expect(ctrl.filterSegments[1].type).toBe('operator');
  197. expect(ctrl.filterSegments[2].type).toBe('value');
  198. expect(ctrl.filterSegments[3].type).toBe('plus-button');
  199. });
  200. });
  201. describe('and user clicks on value segment and value is equal to fake value', () => {
  202. beforeEach(() => {
  203. const existingKeySegment = { value: 'filterkey1', type: 'key' };
  204. const existingOperatorSegment = { value: '=', type: 'operator' };
  205. const existingValueSegment = { value: ctrl.defaultFilterValue, type: 'value' };
  206. ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment];
  207. ctrl.filterSegmentUpdated(existingValueSegment, 2);
  208. });
  209. it('should not add plus segment', () => {
  210. expect(ctrl.filterSegments.length).toBe(3);
  211. expect(ctrl.filterSegments[0].type).toBe('key');
  212. expect(ctrl.filterSegments[1].type).toBe('operator');
  213. expect(ctrl.filterSegments[2].type).toBe('value');
  214. });
  215. });
  216. describe('and user removes key segment', () => {
  217. beforeEach(() => {
  218. const existingKeySegment = { value: ctrl.defaultRemoveFilterValue, type: 'key' };
  219. const existingOperatorSegment = { value: '=', type: 'operator' };
  220. const existingValueSegment = { value: 'filtervalue', type: 'value' };
  221. const plusSegment = { value: '', type: 'plus-button' };
  222. ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment, plusSegment];
  223. ctrl.filterSegmentUpdated(existingKeySegment, 0);
  224. });
  225. it('should remove filter segments', () => {
  226. expect(ctrl.filterSegments.length).toBe(1);
  227. expect(ctrl.filterSegments[0].type).toBe('plus-button');
  228. });
  229. });
  230. describe('and user removes key segment and there is a previous filter', () => {
  231. beforeEach(() => {
  232. const existingKeySegment1 = { value: ctrl.defaultRemoveFilterValue, type: 'key' };
  233. const existingKeySegment2 = { value: ctrl.defaultRemoveFilterValue, type: 'key' };
  234. const existingOperatorSegment = { value: '=', type: 'operator' };
  235. const existingValueSegment = { value: 'filtervalue', type: 'value' };
  236. const conditionSegment = { value: 'AND', type: 'condition' };
  237. const plusSegment = { value: '', type: 'plus-button' };
  238. ctrl.filterSegments = [
  239. existingKeySegment1,
  240. existingOperatorSegment,
  241. existingValueSegment,
  242. conditionSegment,
  243. existingKeySegment2,
  244. Object.assign({}, existingOperatorSegment),
  245. Object.assign({}, existingValueSegment),
  246. plusSegment,
  247. ];
  248. ctrl.filterSegmentUpdated(existingKeySegment2, 4);
  249. });
  250. it('should remove filter segments and the condition segment', () => {
  251. expect(ctrl.filterSegments.length).toBe(4);
  252. expect(ctrl.filterSegments[0].type).toBe('key');
  253. expect(ctrl.filterSegments[1].type).toBe('operator');
  254. expect(ctrl.filterSegments[2].type).toBe('value');
  255. expect(ctrl.filterSegments[3].type).toBe('plus-button');
  256. });
  257. });
  258. describe('and user removes key segment and there is a filter after it', () => {
  259. beforeEach(() => {
  260. const existingKeySegment1 = { value: ctrl.defaultRemoveFilterValue, type: 'key' };
  261. const existingKeySegment2 = { value: ctrl.defaultRemoveFilterValue, type: 'key' };
  262. const existingOperatorSegment = { value: '=', type: 'operator' };
  263. const existingValueSegment = { value: 'filtervalue', type: 'value' };
  264. const conditionSegment = { value: 'AND', type: 'condition' };
  265. const plusSegment = { value: '', type: 'plus-button' };
  266. ctrl.filterSegments = [
  267. existingKeySegment1,
  268. existingOperatorSegment,
  269. existingValueSegment,
  270. conditionSegment,
  271. existingKeySegment2,
  272. Object.assign({}, existingOperatorSegment),
  273. Object.assign({}, existingValueSegment),
  274. plusSegment,
  275. ];
  276. ctrl.filterSegmentUpdated(existingKeySegment1, 0);
  277. });
  278. it('should remove filter segments and the condition segment', () => {
  279. expect(ctrl.filterSegments.length).toBe(4);
  280. expect(ctrl.filterSegments[0].type).toBe('key');
  281. expect(ctrl.filterSegments[1].type).toBe('operator');
  282. expect(ctrl.filterSegments[2].type).toBe('value');
  283. expect(ctrl.filterSegments[3].type).toBe('plus-button');
  284. });
  285. });
  286. describe('and user clicks on plus button', () => {
  287. beforeEach(() => {
  288. const existingKeySegment = { value: 'filterkey1', type: 'key' };
  289. const existingOperatorSegment = { value: '=', type: 'operator' };
  290. const existingValueSegment = { value: 'filtervalue', type: 'value' };
  291. const plusSegment = { value: 'filterkey2', type: 'plus-button' };
  292. ctrl.filterSegments = [existingKeySegment, existingOperatorSegment, existingValueSegment, plusSegment];
  293. ctrl.filterSegmentUpdated(plusSegment, 3);
  294. });
  295. it('should condition segment and new filter segments', () => {
  296. expect(ctrl.filterSegments.length).toBe(7);
  297. expect(ctrl.filterSegments[0].type).toBe('key');
  298. expect(ctrl.filterSegments[1].type).toBe('operator');
  299. expect(ctrl.filterSegments[2].type).toBe('value');
  300. expect(ctrl.filterSegments[3].type).toBe('condition');
  301. expect(ctrl.filterSegments[4].type).toBe('key');
  302. expect(ctrl.filterSegments[5].type).toBe('operator');
  303. expect(ctrl.filterSegments[6].type).toBe('value');
  304. });
  305. });
  306. });
  307. });
  308. });
  309. function createCtrlWithFakes(existingFilters?: string[]) {
  310. StackdriverQueryCtrl.prototype.panelCtrl = {
  311. events: { on: () => {} },
  312. panel: { scopedVars: [], targets: [] },
  313. refresh: () => {},
  314. };
  315. StackdriverQueryCtrl.prototype.target = createTarget(existingFilters);
  316. StackdriverQueryCtrl.prototype.getMetricTypes = () => {
  317. return Promise.resolve();
  318. };
  319. StackdriverQueryCtrl.prototype.getLabels = () => {
  320. return Promise.resolve();
  321. };
  322. const fakeSegmentServer = {
  323. newKey: val => {
  324. return { value: val, type: 'key' };
  325. },
  326. newKeyValue: val => {
  327. return { value: val, type: 'value' };
  328. },
  329. newSegment: obj => {
  330. return { value: obj.value ? obj.value : obj };
  331. },
  332. newOperators: ops => {
  333. return ops.map(o => {
  334. return { type: 'operator', value: o };
  335. });
  336. },
  337. newFake: (value, type, cssClass) => {
  338. return { value, type, cssClass };
  339. },
  340. newOperator: op => {
  341. return { value: op, type: 'operator' };
  342. },
  343. newPlusButton: () => {
  344. return { type: 'plus-button' };
  345. },
  346. newCondition: val => {
  347. return { type: 'condition', value: val };
  348. },
  349. };
  350. return new StackdriverQueryCtrl(null, null, fakeSegmentServer, null, new TemplateSrvStub());
  351. }
  352. function createTarget(existingFilters?: string[]) {
  353. return {
  354. project: {
  355. id: '',
  356. name: '',
  357. },
  358. metricType: 'ametric',
  359. refId: 'A',
  360. aggregation: {
  361. crossSeriesReducer: '',
  362. alignmentPeriod: '',
  363. perSeriesAligner: '',
  364. groupBys: [],
  365. },
  366. filters: existingFilters || [],
  367. aliasBy: '',
  368. };
  369. }