import { addToRichHistory, getRichHistory, updateStarredInRichHistory, updateCommentInRichHistory, mapNumbertoTimeInSlider, createDateStringFromTs, createQueryHeading, deleteAllFromRichHistory, deleteQueryInRichHistory, filterAndSortQueries, SortOrder, MAX_HISTORY_ITEMS, } from './richHistory'; import store from 'app/core/store'; import { dateTime, DataQuery } from '@grafana/data'; import { RichHistoryQuery } from '../../types'; const mock: any = { storedHistory: [ { comment: '', datasourceId: 'datasource historyId', datasourceName: 'datasource history name', queries: [ { expr: 'query1', maxLines: null, refId: '1' }, { expr: 'query2', refId: '2' }, ], sessionName: '', starred: true, ts: 1, }, ], testComment: '', testDatasourceId: 'datasourceId', testDatasourceName: 'datasourceName', testQueries: [ { expr: 'query3', refId: 'B' }, { expr: 'query4', refId: 'C' }, ], testSessionName: '', testStarred: false, }; const key = 'grafana.explore.richHistory'; describe('richHistory', () => { beforeEach(() => { jest.useFakeTimers('modern'); jest.setSystemTime(new Date(1970, 0, 1)); }); afterEach(() => { jest.useRealTimers(); }); describe('addToRichHistory', () => { beforeEach(() => { deleteAllFromRichHistory(); expect(store.exists(key)).toBeFalsy(); }); const expectedResult = [ { comment: mock.testComment, datasourceId: mock.testDatasourceId, datasourceName: mock.testDatasourceName, queries: mock.testQueries, sessionName: mock.testSessionName, starred: mock.testStarred, ts: 2, }, mock.storedHistory[0], ]; it('should append query to query history', () => { Date.now = jest.fn(() => 2); const { richHistory: newHistory } = addToRichHistory( mock.storedHistory, mock.testDatasourceId, mock.testDatasourceName, mock.testQueries, mock.testStarred, mock.testComment, mock.testSessionName, true, true ); expect(newHistory).toEqual(expectedResult); }); it('should save query history to localStorage', () => { Date.now = jest.fn(() => 2); addToRichHistory( mock.storedHistory, mock.testDatasourceId, mock.testDatasourceName, mock.testQueries, mock.testStarred, mock.testComment, mock.testSessionName, true, true ); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)).toMatchObject(expectedResult); }); it('should not append duplicated query to query history', () => { Date.now = jest.fn(() => 2); const { richHistory: newHistory } = addToRichHistory( mock.storedHistory, mock.storedHistory[0].datasourceId, mock.storedHistory[0].datasourceName, [{ expr: 'query1', maxLines: null, refId: 'A' } as DataQuery, { expr: 'query2', refId: 'B' } as DataQuery], mock.testStarred, mock.testComment, mock.testSessionName, true, true ); expect(newHistory).toEqual([mock.storedHistory[0]]); }); it('should not save duplicated query to localStorage', () => { Date.now = jest.fn(() => 2); addToRichHistory( mock.storedHistory, mock.storedHistory[0].datasourceId, mock.storedHistory[0].datasourceName, [{ expr: 'query1', maxLines: null, refId: 'A' } as DataQuery, { expr: 'query2', refId: 'B' } as DataQuery], mock.testStarred, mock.testComment, mock.testSessionName, true, true ); expect(store.exists(key)).toBeFalsy(); }); it('should not save more than MAX_HISTORY_ITEMS', () => { Date.now = jest.fn(() => 2); const extraItems = 100; // the history has more than MAX let history = []; // history = [ { starred: true, comment: "0" }, { starred: false, comment: "1" }, ... ] for (let i = 0; i < MAX_HISTORY_ITEMS + extraItems; i++) { history.push({ starred: i % 2 === 0, comment: i.toString(), queries: [], ts: new Date(2019, 11, 31).getTime(), }); } const starredItemsInHistory = (MAX_HISTORY_ITEMS + extraItems) / 2; const notStarredItemsInHistory = (MAX_HISTORY_ITEMS + extraItems) / 2; expect(history.filter((h) => h.starred)).toHaveLength(starredItemsInHistory); expect(history.filter((h) => !h.starred)).toHaveLength(notStarredItemsInHistory); const { richHistory: newHistory } = addToRichHistory( (history as any) as RichHistoryQuery[], mock.storedHistory[0].datasourceId, mock.storedHistory[0].datasourceName, [{ expr: 'query1', maxLines: null, refId: 'A' } as DataQuery, { expr: 'query2', refId: 'B' } as DataQuery], true, mock.testComment, mock.testSessionName, true, true ); // one not starred replaced with a newly added starred item const removedNotStarredItems = extraItems + 1; // + 1 to make space for the new item expect(newHistory.filter((h) => h.starred)).toHaveLength(starredItemsInHistory + 1); // starred item added expect(newHistory.filter((h) => !h.starred)).toHaveLength(starredItemsInHistory - removedNotStarredItems); }); }); describe('updateStarredInRichHistory', () => { it('should update starred in query in history', () => { const updatedStarred = updateStarredInRichHistory(mock.storedHistory, 1); expect(updatedStarred[0].starred).toEqual(false); }); it('should update starred in localStorage', () => { updateStarredInRichHistory(mock.storedHistory, 1); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)[0].starred).toEqual(false); }); }); describe('updateCommentInRichHistory', () => { it('should update comment in query in history', () => { const updatedComment = updateCommentInRichHistory(mock.storedHistory, 1, 'new comment'); expect(updatedComment[0].comment).toEqual('new comment'); }); it('should update comment in localStorage', () => { updateCommentInRichHistory(mock.storedHistory, 1, 'new comment'); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)[0].comment).toEqual('new comment'); }); }); describe('deleteQueryInRichHistory', () => { it('should delete query in query in history', () => { const deletedHistory = deleteQueryInRichHistory(mock.storedHistory, 1); expect(deletedHistory).toEqual([]); }); it('should delete query in localStorage', () => { deleteQueryInRichHistory(mock.storedHistory, 1); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)).toEqual([]); }); }); describe('mapNumbertoTimeInSlider', () => { it('should correctly map number to value', () => { const value = mapNumbertoTimeInSlider(25); expect(value).toEqual('25 days ago'); }); }); describe('createDateStringFromTs', () => { it('should correctly create string value from timestamp', () => { const value = createDateStringFromTs(1583932327000); expect(value).toEqual('March 11'); }); }); describe('filterQueries', () => { it('should filter out queries based on data source filter', () => { const filteredQueries = filterAndSortQueries( mock.storedHistory, SortOrder.Ascending, ['not provided data source'], '' ); expect(filteredQueries).toHaveLength(0); }); it('should keep queries based on data source filter', () => { const filteredQueries = filterAndSortQueries( mock.storedHistory, SortOrder.Ascending, ['datasource history name'], '' ); expect(filteredQueries).toHaveLength(1); }); it('should filter out all queries based on search filter', () => { const filteredQueries = filterAndSortQueries( mock.storedHistory, SortOrder.Ascending, [], 'i do not exist in query' ); expect(filteredQueries).toHaveLength(0); }); it('should include queries based on search filter', () => { const filteredQueries = filterAndSortQueries(mock.storedHistory, SortOrder.Ascending, [], 'query1'); expect(filteredQueries).toHaveLength(1); }); }); describe('createQueryHeading', () => { it('should correctly create heading for queries when sort order is ascending ', () => { // Have to offset the timezone of a 1 microsecond epoch, and then reverse the changes mock.storedHistory[0].ts = 1 + -1 * dateTime().utcOffset() * 60 * 1000; const heading = createQueryHeading(mock.storedHistory[0], SortOrder.Ascending); expect(heading).toEqual('January 1'); }); it('should correctly create heading for queries when sort order is datasourceAZ ', () => { const heading = createQueryHeading(mock.storedHistory[0], SortOrder.DatasourceAZ); expect(heading).toEqual(mock.storedHistory[0].datasourceName); }); }); describe('getRichHistory', () => { afterEach(() => { deleteAllFromRichHistory(); expect(store.exists(key)).toBeFalsy(); }); describe('should load from localStorage data in old formats', () => { it('should load when queries are strings', () => { const oldHistoryItem = { ...mock.storedHistory[0], queries: ['test query 1', 'test query 2', 'test query 3'] }; store.setObject(key, [oldHistoryItem]); const expectedHistoryItem = { ...mock.storedHistory[0], queries: [ { expr: 'test query 1', refId: 'A', }, { expr: 'test query 2', refId: 'B', }, { expr: 'test query 3', refId: 'C', }, ], }; const result = getRichHistory(); expect(result).toStrictEqual([expectedHistoryItem]); }); it('should load when queries are json-encoded strings', () => { const oldHistoryItem = { ...mock.storedHistory[0], queries: ['{"refId":"A","key":"key1","metrics":[]}', '{"refId":"B","key":"key2","metrics":[]}'], }; store.setObject(key, [oldHistoryItem]); const expectedHistoryItem = { ...mock.storedHistory[0], queries: [ { refId: 'A', key: 'key1', metrics: [], }, { refId: 'B', key: 'key2', metrics: [], }, ], }; const result = getRichHistory(); expect(result).toStrictEqual([expectedHistoryItem]); }); }); }); });