query_ctrl.test.ts 17 KB

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