diff --git a/public/app/core/utils/richHistory.test.ts b/public/app/core/utils/richHistory.test.ts new file mode 100644 index 00000000000..77a639d71cd --- /dev/null +++ b/public/app/core/utils/richHistory.test.ts @@ -0,0 +1,168 @@ +import { + addToRichHistory, + updateStarredInRichHistory, + updateCommentInRichHistory, + mapNumbertoTimeInSlider, + createDateStringFromTs, + createQueryHeading, + createDataQuery, + deleteAllFromRichHistory, +} from './richHistory'; +import store from 'app/core/store'; +import { SortOrder } from './explore'; + +const mock: any = { + history: [ + { + comment: '', + datasourceId: 'datasource historyId', + datasourceName: 'datasource history name', + queries: ['query1', 'query2'], + sessionName: '', + starred: true, + ts: 1, + }, + ], + comment: '', + datasourceId: 'datasourceId', + datasourceName: 'datasourceName', + queries: ['query3'], + sessionName: '', + starred: false, +}; + +const key = 'grafana.explore.richHistory'; + +describe('addToRichHistory', () => { + beforeEach(() => { + deleteAllFromRichHistory(); + expect(store.exists(key)).toBeFalsy(); + }); + + const expectedResult = [ + { + comment: mock.comment, + datasourceId: mock.datasourceId, + datasourceName: mock.datasourceName, + queries: mock.queries, + sessionName: mock.sessionName, + starred: mock.starred, + ts: 2, + }, + mock.history[0], + ]; + + it('should append query to query history', () => { + Date.now = jest.fn(() => 2); + const newHistory = addToRichHistory( + mock.history, + mock.datasourceId, + mock.datasourceName, + mock.queries, + mock.starred, + mock.comment, + mock.sessionName + ); + expect(newHistory).toEqual(expectedResult); + }); + + it('should save query history to localStorage', () => { + Date.now = jest.fn(() => 2); + + addToRichHistory( + mock.history, + mock.datasourceId, + mock.datasourceName, + mock.queries, + mock.starred, + mock.comment, + mock.sessionName + ); + 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 newHistory = addToRichHistory( + mock.history, + mock.history[0].datasourceId, + mock.history[0].datasourceName, + mock.history[0].queries, + mock.starred, + mock.comment, + mock.sessionName + ); + expect(newHistory).toEqual([mock.history[0]]); + }); + + it('should not save duplicated query to localStorage', () => { + Date.now = jest.fn(() => 2); + addToRichHistory( + mock.history, + mock.history[0].datasourceId, + mock.history[0].datasourceName, + mock.history[0].queries, + mock.starred, + mock.comment, + mock.sessionName + ); + expect(store.exists(key)).toBeFalsy(); + }); +}); + +describe('updateStarredInRichHistory', () => { + it('should update starred in query in history', () => { + const updatedStarred = updateStarredInRichHistory(mock.history, 1); + expect(updatedStarred[0].starred).toEqual(false); + }); + it('should update starred in localStorage', () => { + updateStarredInRichHistory(mock.history, 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.history, 1, 'new comment'); + expect(updatedComment[0].comment).toEqual('new comment'); + }); + it('should update comment in localStorage', () => { + updateCommentInRichHistory(mock.history, 1, 'new comment'); + expect(store.exists(key)).toBeTruthy(); + expect(store.getObject(key)[0].comment).toEqual('new comment'); + }); +}); + +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('createQueryHeading', () => { + it('should correctly create heading for queries when sort order is ascending ', () => { + const heading = createQueryHeading(mock.history[0], SortOrder.Ascending); + expect(heading).toEqual('January 1'); + }); + it('should correctly create heading for queries when sort order is datasourceAZ ', () => { + const heading = createQueryHeading(mock.history[0], SortOrder.DatasourceAZ); + expect(heading).toEqual(mock.history[0].datasourceName); + }); +}); + +describe('createDataQuery', () => { + it('should correctly create data query from rich history query', () => { + const dataQuery = createDataQuery(mock.history[0], mock.queries[0], 0); + expect(dataQuery).toEqual({ datasource: 'datasource history name', expr: 'query3', refId: 'A' }); + }); +}); diff --git a/public/app/features/explore/RichHistory/RichHistory.test.tsx b/public/app/features/explore/RichHistory/RichHistory.test.tsx new file mode 100644 index 00000000000..5c576e1701b --- /dev/null +++ b/public/app/features/explore/RichHistory/RichHistory.test.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { GrafanaTheme } from '@grafana/data'; +import { ExploreId } from '../../../types/explore'; +import { RichHistory, RichHistoryProps } from './RichHistory'; +import { Tabs } from './RichHistory'; +import { Tab, Slider } from '@grafana/ui'; + +jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); + +const setup = (propOverrides?: Partial) => { + const props: RichHistoryProps = { + theme: {} as GrafanaTheme, + exploreId: ExploreId.left, + activeDatasourceInstance: 'Test datasource', + richHistory: [], + firstTab: Tabs.RichHistory, + deleteRichHistory: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = mount(); + return wrapper; +}; + +describe('RichHistory', () => { + it('should render all tabs in tab bar', () => { + const wrapper = setup(); + expect(wrapper.find(Tab)).toHaveLength(3); + }); + it('should render correct lebels of tabs in tab bar', () => { + const wrapper = setup(); + expect( + wrapper + .find(Tab) + .at(0) + .text() + ).toEqual('Query history'); + expect( + wrapper + .find(Tab) + .at(1) + .text() + ).toEqual('Starred'); + expect( + wrapper + .find(Tab) + .at(2) + .text() + ).toEqual('Settings'); + }); + it('should correctly render query history tab as active tab', () => { + const wrapper = setup(); + expect(wrapper.find(Slider)).toHaveLength(1); + }); + it('should correctly render starred tab as active tab', () => { + const wrapper = setup({ firstTab: Tabs.Starred }); + expect(wrapper.find(Slider)).toHaveLength(0); + }); +}); diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index 906373a4fed..f8c4ca60ad1 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -30,7 +30,7 @@ export const sortOrderOptions = [ { label: 'Data source Z-A', value: SortOrder.DatasourceZA }, ]; -interface RichHistoryProps extends Themeable { +export interface RichHistoryProps extends Themeable { richHistory: RichHistoryQuery[]; activeDatasourceInstance: string; firstTab: Tabs; diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx new file mode 100644 index 00000000000..c1e5ff90901 --- /dev/null +++ b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { RichHistoryCard, Props } from './RichHistoryCard'; +import { ExploreId } from '../../../types/explore'; +import { DataSourceApi } from '@grafana/data'; + +const setup = (propOverrides?: Partial) => { + const props: Props = { + query: { + ts: 1, + datasourceName: 'Test datasource', + datasourceId: 'datasource 1', + starred: false, + comment: '', + queries: ['query1', 'query2', 'query3'], + sessionName: '', + }, + changeQuery: jest.fn(), + changeDatasource: jest.fn(), + clearQueries: jest.fn(), + updateRichHistory: jest.fn(), + exploreId: ExploreId.left, + datasourceInstance: { name: 'Datasource' } as DataSourceApi, + }; + + Object.assign(props, propOverrides); + + const wrapper = mount(); + return wrapper; +}; + +const starredQueryWithComment = { + ts: 1, + datasourceName: 'Test datasource', + datasourceId: 'datasource 1', + starred: true, + comment: 'test comment', + queries: ['query1', 'query2', 'query3'], + sessionName: '', +}; + +describe('RichHistoryCard', () => { + it('should render all queries', () => { + const wrapper = setup(); + expect(wrapper.find({ 'aria-label': 'Query text' })).toHaveLength(3); + expect( + wrapper + .find({ 'aria-label': 'Query text' }) + .at(0) + .text() + ).toEqual('query1'); + expect( + wrapper + .find({ 'aria-label': 'Query text' }) + .at(1) + .text() + ).toEqual('query2'); + expect( + wrapper + .find({ 'aria-label': 'Query text' }) + .at(2) + .text() + ).toEqual('query3'); + }); + + describe('commenting', () => { + it('should render comment, if comment present', () => { + const wrapper = setup({ query: starredQueryWithComment }); + expect(wrapper.find({ 'aria-label': 'Query comment' })).toHaveLength(1); + expect(wrapper.find({ 'aria-label': 'Query comment' }).text()).toEqual('test comment'); + }); + it('should have title "Edit comment" at comment icon, if comment present', () => { + const wrapper = setup({ query: starredQueryWithComment }); + expect(wrapper.find({ title: 'Edit comment' })).toHaveLength(1); + expect(wrapper.find({ title: 'Add comment' })).toHaveLength(0); + }); + it('should have title "Add comment" at comment icon, if no comment present', () => { + const wrapper = setup(); + expect(wrapper.find({ title: 'Add comment' })).toHaveLength(1); + expect(wrapper.find({ title: 'Edit comment' })).toHaveLength(0); + }); + }); + + describe('starring', () => { + it('should have title "Star query", if not starred', () => { + const wrapper = setup(); + expect(wrapper.find({ title: 'Star query' })).toHaveLength(1); + }); + it('should render fa-star-o icon, if not starred', () => { + const wrapper = setup(); + expect(wrapper.find({ title: 'Star query' }).hasClass('fa-star-o')).toBe(true); + }); + it('should have title "Unstar query", if not starred', () => { + const wrapper = setup({ query: starredQueryWithComment }); + expect(wrapper.find({ title: 'Unstar query' })).toHaveLength(1); + }); + it('should have fa-star icon, if not starred', () => { + const wrapper = setup({ query: starredQueryWithComment }); + expect(wrapper.find({ title: 'Unstar query' }).hasClass('fa-star')).toBe(true); + }); + }); +}); diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.tsx index abc8f43cfd8..bf44f50c059 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.tsx @@ -10,7 +10,7 @@ import appEvents from 'app/core/app_events'; import { StoreState } from 'app/types'; import { changeQuery, changeDatasource, clearQueries, updateRichHistory } from '../state/actions'; -interface Props { +export interface Props { query: RichHistoryQuery; changeQuery: typeof changeQuery; changeDatasource: typeof changeDatasource; @@ -111,12 +111,16 @@ export function RichHistoryCard(props: Props) {
onChangeQuery(query)}> {query.queries.map((q, i) => { return ( -
+
{q}
); })} - {!activeUpdateComment && query.comment &&
{query.comment}
} + {!activeUpdateComment && query.comment && ( +
+ {query.comment} +
+ )} {activeUpdateComment && (
({ getExploreDatasources: jest.fn() })); + +const setup = (propOverrides?: Partial) => { + const props: Props = { + width: 500, + exploreId: ExploreId.left, + activeDatasourceInstance: 'Test datasource', + richHistory: [], + firstTab: Tabs.RichHistory, + deleteRichHistory: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = mount(); + return wrapper; +}; + +describe('RichHistoryContainer', () => { + it('should render reseizable component', () => { + const wrapper = setup(); + expect(wrapper.find(Resizable)).toHaveLength(1); + }); + it('should render component with correct width', () => { + const wrapper = setup(); + expect(wrapper.getDOMNode().getAttribute('style')).toContain('width: 531.5px'); + }); + it('should render component with correct height', () => { + const wrapper = setup(); + expect(wrapper.getDOMNode().getAttribute('style')).toContain('height: 400px'); + }); +}); diff --git a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx index 51366b53c9e..4c6fc3dacd4 100644 --- a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx @@ -77,7 +77,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { }; }); -interface Props { +export interface Props { width: number; exploreId: ExploreId; activeDatasourceInstance: string; @@ -87,7 +87,7 @@ interface Props { onClose: () => void; } -function RichHistoryContainer(props: Props) { +export function RichHistoryContainer(props: Props) { const [visible, setVisible] = useState(false); const [height, setHeight] = useState(400); diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.test.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.test.tsx new file mode 100644 index 00000000000..83d21a6818d --- /dev/null +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.test.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { ExploreId } from '../../../types/explore'; +import { SortOrder } from 'app/core/utils/explore'; +import { RichHistoryQueriesTab, Props } from './RichHistoryQueriesTab'; +import { Slider } from '@grafana/ui'; + +jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); + +const setup = (propOverrides?: Partial) => { + const props: Props = { + queries: [], + sortOrder: SortOrder.Ascending, + activeDatasourceOnly: false, + datasourceFilters: null, + retentionPeriod: 14, + exploreId: ExploreId.left, + onChangeSortOrder: jest.fn(), + onSelectDatasourceFilters: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = mount(); + return wrapper; +}; + +describe('RichHistoryQueriesTab', () => { + describe('slider', () => { + it('should render slider', () => { + const wrapper = setup(); + expect(wrapper.find(Slider)).toHaveLength(1); + }); + it('should render slider with correct timerange', () => { + const wrapper = setup(); + expect( + wrapper + .find('.label-slider') + .at(1) + .text() + ).toEqual('today'); + expect( + wrapper + .find('.label-slider') + .at(2) + .text() + ).toEqual('two weeks ago'); + }); + }); + + describe('sort options', () => { + it('should render sorter', () => { + const wrapper = setup(); + expect(wrapper.find({ 'aria-label': 'Sort queries' })).toHaveLength(1); + }); + }); + + describe('select datasource', () => { + it('should render select datasource if activeDatasourceOnly is false', () => { + const wrapper = setup(); + expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(1); + }); + it('should not render select datasource if activeDatasourceOnly is true', () => { + const wrapper = setup({ activeDatasourceOnly: true }); + expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(0); + }); + }); +}); diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx index fb65f2d933c..5d2f8a7a4fd 100644 --- a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx @@ -23,7 +23,7 @@ import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; import { Select, Slider } from '@grafana/ui'; -interface Props { +export interface Props { queries: RichHistoryQuery[]; sortOrder: SortOrder; activeDatasourceOnly: boolean; @@ -190,7 +190,7 @@ export function RichHistoryQueriesTab(props: Props) {
{!activeDatasourceOnly && ( -
+
order.value === sortOrder)} options={sortOrderOptions} diff --git a/public/app/features/explore/RichHistory/RichHistorySettings.test.tsx b/public/app/features/explore/RichHistory/RichHistorySettings.test.tsx new file mode 100644 index 00000000000..1e1257d11ae --- /dev/null +++ b/public/app/features/explore/RichHistory/RichHistorySettings.test.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { RichHistorySettings, RichHistorySettingsProps } from './RichHistorySettings'; +import { Forms } from '@grafana/ui'; + +const setup = (propOverrides?: Partial) => { + const props: RichHistorySettingsProps = { + retentionPeriod: 14, + starredTabAsFirstTab: true, + activeDatasourceOnly: false, + onChangeRetentionPeriod: jest.fn(), + toggleStarredTabAsFirstTab: jest.fn(), + toggleactiveDatasourceOnly: jest.fn(), + deleteRichHistory: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = mount(); + return wrapper; +}; + +describe('RichHistorySettings', () => { + it('should render component with correct retention period', () => { + const wrapper = setup(); + expect(wrapper.find(Forms.Select).text()).toEqual('2 weeks'); + }); + it('should render component with correctly checked starredTabAsFirstTab settings', () => { + const wrapper = setup(); + expect( + wrapper + .find(Forms.Switch) + .at(0) + .prop('value') + ).toBe(true); + }); + it('should render component with correctly not checked toggleactiveDatasourceOnly settings', () => { + const wrapper = setup(); + expect( + wrapper + .find(Forms.Switch) + .at(1) + .prop('value') + ).toBe(false); + }); +}); diff --git a/public/app/features/explore/RichHistory/RichHistorySettings.tsx b/public/app/features/explore/RichHistory/RichHistorySettings.tsx index 08a8ebcddec..1828f7d282e 100644 --- a/public/app/features/explore/RichHistory/RichHistorySettings.tsx +++ b/public/app/features/explore/RichHistory/RichHistorySettings.tsx @@ -5,7 +5,7 @@ import { GrafanaTheme, AppEvents } from '@grafana/data'; import appEvents from 'app/core/app_events'; import { CoreEvents } from 'app/types'; -interface RichHistorySettingsProps { +export interface RichHistorySettingsProps { retentionPeriod: number; starredTabAsFirstTab: boolean; activeDatasourceOnly: boolean; diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx new file mode 100644 index 00000000000..f1dc2df53b2 --- /dev/null +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.test.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { ExploreId } from '../../../types/explore'; +import { SortOrder } from 'app/core/utils/explore'; +import { RichHistoryStarredTab, Props } from './RichHistoryStarredTab'; + +jest.mock('../state/selectors', () => ({ getExploreDatasources: jest.fn() })); + +const setup = (propOverrides?: Partial) => { + const props: Props = { + queries: [], + sortOrder: SortOrder.Ascending, + activeDatasourceOnly: false, + datasourceFilters: null, + exploreId: ExploreId.left, + onChangeSortOrder: jest.fn(), + onSelectDatasourceFilters: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = mount(); + return wrapper; +}; + +describe('RichHistoryStarredTab', () => { + describe('sorter', () => { + it('should render sorter', () => { + const wrapper = setup(); + expect(wrapper.find({ 'aria-label': 'Sort queries' })).toHaveLength(1); + }); + }); + + describe('select datasource', () => { + it('should render select datasource if activeDatasourceOnly is false', () => { + const wrapper = setup(); + expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(1); + }); + + it('should not render select datasource if activeDatasourceOnly is true', () => { + const wrapper = setup({ activeDatasourceOnly: true }); + expect(wrapper.find({ 'aria-label': 'Filter datasources' })).toHaveLength(0); + }); + }); +}); diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx index 0f4cf959b09..bcc2d7ea6cb 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx @@ -18,7 +18,7 @@ import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; import { Select } from '@grafana/ui'; -interface Props { +export interface Props { queries: RichHistoryQuery[]; sortOrder: SortOrder; activeDatasourceOnly: boolean; @@ -111,7 +111,7 @@ export function RichHistoryStarredTab(props: Props) {
{!activeDatasourceOnly && ( -
+
order.value === sortOrder)}