mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			594 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			594 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
| import { render, screen, waitFor, fireEvent, userEvent } from 'test/test-utils';
 | |
| 
 | |
| import {
 | |
|   createDataFrame,
 | |
|   FieldType,
 | |
|   LogRowContextQueryDirection,
 | |
|   LogsSortOrder,
 | |
|   SplitOpenOptions,
 | |
| } from '@grafana/data';
 | |
| 
 | |
| import { dataFrameToLogsModel } from '../../logsModel';
 | |
| 
 | |
| import { LogRowContextModal } from './LogRowContextModal';
 | |
| 
 | |
| const dfBefore = createDataFrame({
 | |
|   fields: [
 | |
|     {
 | |
|       name: 'time',
 | |
|       type: FieldType.time,
 | |
|       values: ['2019-04-26T07:28:11.352440161Z', '2019-04-26T09:28:11.352440161Z'],
 | |
|     },
 | |
|     {
 | |
|       name: 'message',
 | |
|       type: FieldType.string,
 | |
|       values: ['foo123', 'foo123'],
 | |
|     },
 | |
|   ],
 | |
| });
 | |
| const dfNow = createDataFrame({
 | |
|   fields: [
 | |
|     {
 | |
|       name: 'time',
 | |
|       type: FieldType.time,
 | |
|       values: ['2019-04-26T09:28:11.352440161Z'],
 | |
|     },
 | |
|     {
 | |
|       name: 'message',
 | |
|       type: FieldType.string,
 | |
|       values: ['foo123'],
 | |
|     },
 | |
|   ],
 | |
| });
 | |
| const dfAfter = createDataFrame({
 | |
|   fields: [
 | |
|     {
 | |
|       name: 'time',
 | |
|       type: FieldType.time,
 | |
|       values: ['2019-04-26T14:42:50.991981292Z', '2019-04-26T16:28:11.352440161Z'],
 | |
|     },
 | |
|     {
 | |
|       name: 'message',
 | |
|       type: FieldType.string,
 | |
|       values: ['foo123', 'bar123'],
 | |
|     },
 | |
|   ],
 | |
| });
 | |
| 
 | |
| let getRowContext = jest.fn();
 | |
| const dispatchMock = jest.fn();
 | |
| jest.mock('app/types/store', () => ({
 | |
|   ...jest.requireActual('app/types/store'),
 | |
|   useDispatch: () => dispatchMock,
 | |
| }));
 | |
| 
 | |
| const splitOpenSym = Symbol('splitOpen');
 | |
| const splitOpen = jest.fn().mockReturnValue(splitOpenSym);
 | |
| jest.mock('app/features/explore/state/main', () => ({
 | |
|   ...jest.requireActual('app/features/explore/state/main'),
 | |
|   splitOpen: (arg?: SplitOpenOptions) => {
 | |
|     return splitOpen(arg);
 | |
|   },
 | |
| }));
 | |
| 
 | |
| const logs = dataFrameToLogsModel([dfNow]);
 | |
| const row = logs.rows[0];
 | |
| 
 | |
| const timeZone = 'UTC';
 | |
| 
 | |
| describe('LogRowContextModal', () => {
 | |
|   const originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView;
 | |
|   let uniqueRefIdCounter = 1;
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     window.HTMLElement.prototype.scrollIntoView = jest.fn();
 | |
|     uniqueRefIdCounter = 1;
 | |
|     getRowContext = jest.fn().mockImplementation(async (_, options) => {
 | |
|       uniqueRefIdCounter += 1;
 | |
|       const refId = `refid_${uniqueRefIdCounter}`;
 | |
|       if (options.direction === LogRowContextQueryDirection.Forward) {
 | |
|         return {
 | |
|           data: [
 | |
|             {
 | |
|               refId,
 | |
|               ...dfBefore,
 | |
|             },
 | |
|           ],
 | |
|         };
 | |
|       } else {
 | |
|         return {
 | |
|           data: [
 | |
|             {
 | |
|               refId,
 | |
|               ...dfAfter,
 | |
|             },
 | |
|           ],
 | |
|         };
 | |
|       }
 | |
|     });
 | |
|   });
 | |
|   afterEach(() => {
 | |
|     window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
 | |
|     jest.clearAllMocks();
 | |
|   });
 | |
| 
 | |
|   it('should not render modal when it is closed', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={false}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() => expect(screen.queryByText('Log context')).not.toBeInTheDocument());
 | |
|   });
 | |
| 
 | |
|   it('should render modal when it is open', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() => expect(screen.queryByText('Log context')).toBeInTheDocument());
 | |
|   });
 | |
| 
 | |
|   it('should call getRowContext on open and change of row', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={false}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() => expect(getRowContext).not.toHaveBeenCalled());
 | |
|   });
 | |
| 
 | |
|   it('should call getRowContext on open', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
|     await waitFor(() => expect(getRowContext).toHaveBeenCalledTimes(2));
 | |
|   });
 | |
| 
 | |
|   it('should render 3 lines containing `foo123`', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
|     // there need to be 2 lines with that message. 1 in before, 1 in now, 1 in after
 | |
|     await waitFor(() => expect(screen.getAllByText('foo123').length).toBe(3));
 | |
|   });
 | |
| 
 | |
|   it('should render 3 lines containing `foo123` with the same ms timestamp', async () => {
 | |
|     const dfBeforeNs = createDataFrame({
 | |
|       fields: [
 | |
|         {
 | |
|           name: 'time',
 | |
|           type: FieldType.time,
 | |
|           values: [1, 1],
 | |
|         },
 | |
|         {
 | |
|           name: 'message',
 | |
|           type: FieldType.string,
 | |
|           values: ['foo123', 'foo123'],
 | |
|         },
 | |
|         {
 | |
|           name: 'tsNs',
 | |
|           type: FieldType.string,
 | |
|           values: ['1', '2'],
 | |
|         },
 | |
|       ],
 | |
|     });
 | |
|     const dfNowNs = createDataFrame({
 | |
|       fields: [
 | |
|         {
 | |
|           name: 'time',
 | |
|           type: FieldType.time,
 | |
|           values: [1],
 | |
|         },
 | |
|         {
 | |
|           name: 'message',
 | |
|           type: FieldType.string,
 | |
|           values: ['foo123'],
 | |
|         },
 | |
|         {
 | |
|           name: 'tsNs',
 | |
|           type: FieldType.string,
 | |
|           values: ['2'],
 | |
|         },
 | |
|       ],
 | |
|     });
 | |
|     const dfAfterNs = createDataFrame({
 | |
|       fields: [
 | |
|         {
 | |
|           name: 'time',
 | |
|           type: FieldType.time,
 | |
|           values: [1, 1],
 | |
|         },
 | |
|         {
 | |
|           name: 'message',
 | |
|           type: FieldType.string,
 | |
|           values: ['foo123', 'foo123'],
 | |
|         },
 | |
|         {
 | |
|           name: 'tsNs',
 | |
|           type: FieldType.string,
 | |
|           values: ['2', '3'],
 | |
|         },
 | |
|       ],
 | |
|     });
 | |
| 
 | |
|     let uniqueRefIdCounter = 1;
 | |
|     const logs = dataFrameToLogsModel([dfNowNs]);
 | |
|     const row = logs.rows[0];
 | |
|     const getRowContext = jest.fn().mockImplementation(async (_, options) => {
 | |
|       uniqueRefIdCounter += 1;
 | |
|       const refId = `refid_${uniqueRefIdCounter}`;
 | |
|       if (uniqueRefIdCounter === 2) {
 | |
|         return {
 | |
|           data: [
 | |
|             {
 | |
|               refId,
 | |
|               ...dfBeforeNs,
 | |
|             },
 | |
|           ],
 | |
|         };
 | |
|       } else if (uniqueRefIdCounter === 3) {
 | |
|         return {
 | |
|           data: [
 | |
|             {
 | |
|               refId,
 | |
|               ...dfAfterNs,
 | |
|             },
 | |
|           ],
 | |
|         };
 | |
|       }
 | |
|       return { data: [] };
 | |
|     });
 | |
| 
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     // there need to be 3 lines with that message. 1 in before, 1 in now, 1 in after
 | |
|     await waitFor(() => {
 | |
|       expect(screen.getAllByText('foo123').length).toBe(3);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should highlight the same `foo123` searchwords', async () => {
 | |
|     const dfBeforeNs = createDataFrame({
 | |
|       fields: [
 | |
|         {
 | |
|           name: 'time',
 | |
|           type: FieldType.time,
 | |
|           values: [1, 1],
 | |
|         },
 | |
|         {
 | |
|           name: 'message',
 | |
|           type: FieldType.string,
 | |
|           values: ['this contains foo123', 'this contains foo123'],
 | |
|         },
 | |
|         {
 | |
|           name: 'tsNs',
 | |
|           type: FieldType.string,
 | |
|           values: ['1', '2'],
 | |
|         },
 | |
|       ],
 | |
|     });
 | |
|     const dfNowNs = createDataFrame({
 | |
|       fields: [
 | |
|         {
 | |
|           name: 'time',
 | |
|           type: FieldType.time,
 | |
|           values: [1],
 | |
|         },
 | |
|         {
 | |
|           name: 'message',
 | |
|           type: FieldType.string,
 | |
|           values: ['this contains foo123'],
 | |
|         },
 | |
|         {
 | |
|           name: 'tsNs',
 | |
|           type: FieldType.string,
 | |
|           values: ['2'],
 | |
|         },
 | |
|       ],
 | |
|     });
 | |
|     const dfAfterNs = createDataFrame({
 | |
|       fields: [
 | |
|         {
 | |
|           name: 'time',
 | |
|           type: FieldType.time,
 | |
|           values: [1, 1],
 | |
|         },
 | |
|         {
 | |
|           name: 'message',
 | |
|           type: FieldType.string,
 | |
|           values: ['this contains foo123', 'this contains foo123'],
 | |
|         },
 | |
|         {
 | |
|           name: 'tsNs',
 | |
|           type: FieldType.string,
 | |
|           values: ['2', '3'],
 | |
|         },
 | |
|       ],
 | |
|     });
 | |
| 
 | |
|     let uniqueRefIdCounter = 1;
 | |
|     const logs = dataFrameToLogsModel([dfNowNs]);
 | |
|     const row = logs.rows[0];
 | |
|     row.searchWords = ['foo123'];
 | |
|     const getRowContext = jest.fn().mockImplementation(async (_, options) => {
 | |
|       uniqueRefIdCounter += 1;
 | |
|       const refId = `refid_${uniqueRefIdCounter}`;
 | |
|       if (uniqueRefIdCounter === 2) {
 | |
|         return {
 | |
|           data: [
 | |
|             {
 | |
|               refId,
 | |
|               ...dfBeforeNs,
 | |
|             },
 | |
|           ],
 | |
|         };
 | |
|       } else if (uniqueRefIdCounter === 3) {
 | |
|         return {
 | |
|           data: [
 | |
|             {
 | |
|               refId,
 | |
|               ...dfAfterNs,
 | |
|             },
 | |
|           ],
 | |
|         };
 | |
|       }
 | |
|       return { data: [] };
 | |
|     });
 | |
| 
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     // there need to be 3 lines with that message, all `foo123` should be highlighted
 | |
|     await waitFor(() => {
 | |
|       expect(screen.getAllByText('foo123').length).toBe(3);
 | |
|       for (const el of screen.getAllByText('foo123')) {
 | |
|         // highlights are done in `<mark>` tags
 | |
|         expect(el.tagName).toBe('MARK');
 | |
|       }
 | |
|       // test that the rest is not in a MARK
 | |
|       expect(screen.getAllByText('this contains')[0].tagName).not.toBe('MARK');
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should show a split view button', async () => {
 | |
|     const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
 | |
| 
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         getRowContextQuery={getRowContextQuery}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() =>
 | |
|       expect(
 | |
|         screen.getByRole('button', {
 | |
|           name: /open in split view/i,
 | |
|         })
 | |
|       ).toBeInTheDocument()
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   it('should not show a split view button', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() => {
 | |
|       expect(
 | |
|         screen.queryByRole('button', {
 | |
|           name: /open in split view/i,
 | |
|         })
 | |
|       ).not.toBeInTheDocument();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should call getRowContextQuery', async () => {
 | |
|     const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         getRowContextQuery={getRowContextQuery}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() => expect(getRowContextQuery).toHaveBeenCalledTimes(2));
 | |
|   });
 | |
| 
 | |
|   it('should close modal', async () => {
 | |
|     const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
 | |
|     const onClose = jest.fn();
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={onClose}
 | |
|         getRowContext={getRowContext}
 | |
|         getRowContextQuery={getRowContextQuery}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     const splitViewButton = await screen.findByRole('button', {
 | |
|       name: /open in split view/i,
 | |
|     });
 | |
| 
 | |
|     await userEvent.click(splitViewButton);
 | |
| 
 | |
|     await waitFor(() => expect(onClose).toHaveBeenCalled());
 | |
|   });
 | |
| 
 | |
|   it('should create correct splitOpen', async () => {
 | |
|     const queryObj = { datasource: { uid: 'test-uid' } };
 | |
|     const getRowContextQuery = jest.fn().mockResolvedValue(queryObj);
 | |
|     const onClose = jest.fn();
 | |
| 
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={onClose}
 | |
|         getRowContext={getRowContext}
 | |
|         getRowContextQuery={getRowContextQuery}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     const splitViewButton = await screen.findByRole('button', {
 | |
|       name: /open in split view/i,
 | |
|     });
 | |
| 
 | |
|     await userEvent.click(splitViewButton);
 | |
| 
 | |
|     await waitFor(() =>
 | |
|       expect(splitOpen).toHaveBeenCalledWith(
 | |
|         expect.objectContaining({
 | |
|           queries: [queryObj],
 | |
|           panelsState: {
 | |
|             logs: {
 | |
|               id: row.uid,
 | |
|             },
 | |
|           },
 | |
|         })
 | |
|       )
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   it('should dispatch splitOpen', async () => {
 | |
|     const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
 | |
|     const onClose = jest.fn();
 | |
| 
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={onClose}
 | |
|         getRowContext={getRowContext}
 | |
|         getRowContextQuery={getRowContextQuery}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     const splitViewButton = await screen.findByRole('button', {
 | |
|       name: /open in split view/i,
 | |
|     });
 | |
| 
 | |
|     await userEvent.click(splitViewButton);
 | |
| 
 | |
|     await waitFor(() => expect(dispatchMock).toHaveBeenCalledWith(splitOpenSym));
 | |
|   });
 | |
| 
 | |
|   it('should make the center row sticky on load', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() => {
 | |
|       const rows = screen.getByTestId('entry-row');
 | |
|       expect(rows).toHaveStyle('position: sticky');
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   it('should make the center row unsticky on unPinClick', async () => {
 | |
|     render(
 | |
|       <LogRowContextModal
 | |
|         row={row}
 | |
|         open={true}
 | |
|         onClose={() => {}}
 | |
|         getRowContext={getRowContext}
 | |
|         timeZone={timeZone}
 | |
|         logsSortOrder={LogsSortOrder.Descending}
 | |
|       />
 | |
|     );
 | |
| 
 | |
|     await waitFor(() => {
 | |
|       const rows = screen.getByTestId('entry-row');
 | |
|       expect(rows).toHaveStyle('position: sticky');
 | |
|     });
 | |
|     const unpinButtons = screen.getAllByLabelText('Unpin line')[0];
 | |
|     fireEvent.click(unpinButtons);
 | |
|     await waitFor(() => {
 | |
|       const rows = screen.getByTestId('entry-row');
 | |
|       expect(rows).not.toHaveStyle('position: sticky');
 | |
|     });
 | |
|   });
 | |
| });
 |