Wrapper.test.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import { fireEvent, screen, waitFor } from '@testing-library/react';
  2. import userEvent from '@testing-library/user-event';
  3. import React from 'react';
  4. import { serializeStateToUrlParam } from '@grafana/data';
  5. import { locationService } from '@grafana/runtime';
  6. import { changeDatasource } from './spec/helper/interactions';
  7. import { makeLogsQueryResponse, makeMetricsQueryResponse } from './spec/helper/query';
  8. import { setupExplore, tearDown, waitForExplore } from './spec/helper/setup';
  9. import { splitOpen } from './state/main';
  10. type Mock = jest.Mock;
  11. jest.mock('app/core/core', () => {
  12. return {
  13. contextSrv: {
  14. hasPermission: () => true,
  15. },
  16. appEvents: {
  17. subscribe: () => {},
  18. publish: () => {},
  19. },
  20. };
  21. });
  22. jest.mock('react-virtualized-auto-sizer', () => {
  23. return {
  24. __esModule: true,
  25. default(props: any) {
  26. return <div>{props.children({ width: 1000 })}</div>;
  27. },
  28. };
  29. });
  30. describe('Wrapper', () => {
  31. afterEach(() => {
  32. tearDown();
  33. });
  34. it('shows warning if there are no data sources', async () => {
  35. setupExplore({ datasources: [] });
  36. // Will throw if isn't found
  37. screen.getByText(/Explore requires at least one data source/i);
  38. });
  39. it('inits url and renders editor but does not call query on empty url', async () => {
  40. const { datasources } = setupExplore();
  41. await waitForExplore();
  42. // At this point url should be initialised to some defaults
  43. expect(locationService.getSearchObject()).toEqual({
  44. orgId: '1',
  45. left: serializeStateToUrlParam({
  46. datasource: 'loki',
  47. queries: [{ refId: 'A' }],
  48. range: { from: 'now-1h', to: 'now' },
  49. }),
  50. });
  51. expect(datasources.loki.query).not.toBeCalled();
  52. });
  53. it('runs query when url contains query and renders results', async () => {
  54. const urlParams = {
  55. left: serializeStateToUrlParam({
  56. datasource: 'loki',
  57. queries: [{ refId: 'A', expr: '{ label="value"}' }],
  58. range: { from: 'now-1h', to: 'now' },
  59. }),
  60. };
  61. const { datasources } = setupExplore({ urlParams });
  62. (datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
  63. // Make sure we render the logs panel
  64. await screen.findByText(/^Logs$/);
  65. // Make sure we render the log line
  66. await screen.findByText(/custom log line/i);
  67. // And that the editor gets the expr from the url
  68. await screen.findByText(`loki Editor input: { label="value"}`);
  69. // We did not change the url
  70. expect(locationService.getSearchObject()).toEqual({
  71. orgId: '1',
  72. ...urlParams,
  73. });
  74. // We called the data source query method once
  75. expect(datasources.loki.query).toBeCalledTimes(1);
  76. expect((datasources.loki.query as Mock).mock.calls[0][0]).toMatchObject({
  77. targets: [{ expr: '{ label="value"}' }],
  78. });
  79. });
  80. it('handles url change and runs the new query', async () => {
  81. const urlParams = { left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]) };
  82. const { datasources } = setupExplore({ urlParams });
  83. (datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
  84. // Wait for rendering the logs
  85. await screen.findByText(/custom log line/i);
  86. (datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse('different log'));
  87. locationService.partial({
  88. left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="different"}' }]),
  89. });
  90. // Editor renders the new query
  91. await screen.findByText(`loki Editor input: { label="different"}`);
  92. // Renders new response
  93. await screen.findByText(/different log/i);
  94. });
  95. it('handles url change and runs the new query with different datasource', async () => {
  96. const urlParams = { left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]) };
  97. const { datasources } = setupExplore({ urlParams });
  98. (datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
  99. // Wait for rendering the logs
  100. await screen.findByText(/custom log line/i);
  101. await screen.findByText(`loki Editor input: { label="value"}`);
  102. (datasources.elastic.query as Mock).mockReturnValueOnce(makeMetricsQueryResponse());
  103. locationService.partial({
  104. left: JSON.stringify(['now-1h', 'now', 'elastic', { expr: 'other query' }]),
  105. });
  106. // Editor renders the new query
  107. await screen.findByText(`elastic Editor input: other query`);
  108. // Renders graph
  109. await screen.findByText(/Graph/i);
  110. });
  111. it('handles changing the datasource manually', async () => {
  112. const urlParams = { left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}', refId: 'A' }]) };
  113. const { datasources } = setupExplore({ urlParams });
  114. (datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
  115. await waitForExplore();
  116. await changeDatasource('elastic');
  117. await screen.findByText('elastic Editor input:');
  118. expect(datasources.elastic.query).not.toBeCalled();
  119. expect(locationService.getSearchObject()).toEqual({
  120. orgId: '1',
  121. left: serializeStateToUrlParam({
  122. datasource: 'elastic',
  123. queries: [{ refId: 'A' }],
  124. range: { from: 'now-1h', to: 'now' },
  125. }),
  126. });
  127. });
  128. it('opens the split pane when split button is clicked', async () => {
  129. setupExplore();
  130. // Wait for rendering the editor
  131. const splitButton = await screen.findByText(/split/i);
  132. fireEvent.click(splitButton);
  133. await waitFor(() => {
  134. const editors = screen.getAllByText('loki Editor input:');
  135. expect(editors.length).toBe(2);
  136. });
  137. });
  138. it('inits with two panes if specified in url', async () => {
  139. const urlParams = {
  140. left: serializeStateToUrlParam({
  141. datasource: 'loki',
  142. queries: [{ refId: 'A', expr: '{ label="value"}' }],
  143. range: { from: 'now-1h', to: 'now' },
  144. }),
  145. right: serializeStateToUrlParam({
  146. datasource: 'elastic',
  147. queries: [{ refId: 'A', expr: 'error' }],
  148. range: { from: 'now-1h', to: 'now' },
  149. }),
  150. };
  151. const { datasources } = setupExplore({ urlParams });
  152. (datasources.loki.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
  153. (datasources.elastic.query as Mock).mockReturnValueOnce(makeLogsQueryResponse());
  154. // Make sure we render the logs panel
  155. await waitFor(() => {
  156. const logsPanels = screen.getAllByText(/^Logs$/);
  157. expect(logsPanels.length).toBe(2);
  158. });
  159. // Make sure we render the log line
  160. const logsLines = await screen.findAllByText(/custom log line/i);
  161. expect(logsLines.length).toBe(2);
  162. // And that the editor gets the expr from the url
  163. await screen.findByText(`loki Editor input: { label="value"}`);
  164. await screen.findByText(`elastic Editor input: error`);
  165. // We did not change the url
  166. expect(locationService.getSearchObject()).toEqual({
  167. orgId: '1',
  168. ...urlParams,
  169. });
  170. // We called the data source query method once
  171. expect(datasources.loki.query).toBeCalledTimes(1);
  172. expect((datasources.loki.query as Mock).mock.calls[0][0]).toMatchObject({
  173. targets: [{ expr: '{ label="value"}' }],
  174. });
  175. expect(datasources.elastic.query).toBeCalledTimes(1);
  176. expect((datasources.elastic.query as Mock).mock.calls[0][0]).toMatchObject({
  177. targets: [{ expr: 'error' }],
  178. });
  179. });
  180. it('can close a pane from a split', async () => {
  181. const urlParams = {
  182. left: JSON.stringify(['now-1h', 'now', 'loki', { refId: 'A' }]),
  183. right: JSON.stringify(['now-1h', 'now', 'elastic', { refId: 'A' }]),
  184. };
  185. setupExplore({ urlParams });
  186. const closeButtons = await screen.findAllByTitle(/Close split pane/i);
  187. await userEvent.click(closeButtons[1]);
  188. await waitFor(() => {
  189. const logsPanels = screen.queryAllByTitle(/Close split pane/i);
  190. expect(logsPanels.length).toBe(0);
  191. });
  192. });
  193. it('handles url change to split view', async () => {
  194. const urlParams = {
  195. left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
  196. };
  197. const { datasources } = setupExplore({ urlParams });
  198. (datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
  199. (datasources.elastic.query as Mock).mockReturnValue(makeLogsQueryResponse());
  200. locationService.partial({
  201. left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
  202. right: JSON.stringify(['now-1h', 'now', 'elastic', { expr: 'error' }]),
  203. });
  204. // Editor renders the new query
  205. await screen.findByText(`loki Editor input: { label="value"}`);
  206. await screen.findByText(`elastic Editor input: error`);
  207. });
  208. it('handles opening split with split open func', async () => {
  209. const urlParams = {
  210. left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
  211. };
  212. const { datasources, store } = setupExplore({ urlParams });
  213. (datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
  214. (datasources.elastic.query as Mock).mockReturnValue(makeLogsQueryResponse());
  215. // This is mainly to wait for render so that the left pane state is initialized as that is needed for splitOpen
  216. // to work
  217. await screen.findByText(`loki Editor input: { label="value"}`);
  218. store.dispatch(splitOpen<any>({ datasourceUid: 'elastic', query: { expr: 'error' } }) as any);
  219. // Editor renders the new query
  220. await screen.findByText(`elastic Editor input: error`);
  221. await screen.findByText(`loki Editor input: { label="value"}`);
  222. });
  223. it('changes the document title of the explore page to include the datasource in use', async () => {
  224. const urlParams = {
  225. left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
  226. };
  227. const { datasources } = setupExplore({ urlParams });
  228. (datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
  229. // This is mainly to wait for render so that the left pane state is initialized as that is needed for the title
  230. // to include the datasource
  231. await screen.findByText(`loki Editor input: { label="value"}`);
  232. await waitFor(() => expect(document.title).toEqual('Explore - loki - Grafana'));
  233. });
  234. it('changes the document title to include the two datasources in use in split view mode', async () => {
  235. const urlParams = {
  236. left: JSON.stringify(['now-1h', 'now', 'loki', { expr: '{ label="value"}' }]),
  237. };
  238. const { datasources, store } = setupExplore({ urlParams });
  239. (datasources.loki.query as Mock).mockReturnValue(makeLogsQueryResponse());
  240. (datasources.elastic.query as Mock).mockReturnValue(makeLogsQueryResponse());
  241. // This is mainly to wait for render so that the left pane state is initialized as that is needed for splitOpen
  242. // to work
  243. await screen.findByText(`loki Editor input: { label="value"}`);
  244. store.dispatch(splitOpen<any>({ datasourceUid: 'elastic', query: { expr: 'error' } }) as any);
  245. await waitFor(() => expect(document.title).toEqual('Explore - loki | elastic - Grafana'));
  246. });
  247. it('removes `from` and `to` parameters from url when first mounted', async () => {
  248. setupExplore({ searchParams: 'from=1&to=2&orgId=1' });
  249. expect(locationService.getSearchObject()).toEqual(expect.not.objectContaining({ from: '1', to: '2' }));
  250. expect(locationService.getSearchObject()).toEqual(expect.objectContaining({ orgId: '1' }));
  251. });
  252. });