From 66e5a1c0b3e37ce28d470f319969475c87122708 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Fri, 24 Apr 2020 18:23:31 +0200 Subject: [PATCH] Rich history: Fix create url and run query & style updates (#23627) * Styling updates * Create getQueryFromDisplayText method for Jaeger, Loki, Prometheus * Fix createLink and runQuery methods for all datasources * Update test * Update Select from Legacy to current * Update filtering * Update public/app/core/utils/richHistory.test.ts * Fix strictnullcheck errors * Remove getQueryFromDisplayText method, as not needed * Update saving of full query and use displayText for formatting * Update tests * Refactor create data query * Remove parsing, store object instead * Fix formatting error * Remove object checking, transform everything to DataQuery * Remove console.log * Rename migrate function, add datasourceName as a useEffect dependency * Fix z-index, move query --- public/app/core/utils/richHistory.test.ts | 124 +++++++++-------- public/app/core/utils/richHistory.ts | 125 ++++++++++++------ .../explore/RichHistory/RichHistory.tsx | 20 ++- .../RichHistory/RichHistoryCard.test.tsx | 38 +++--- .../explore/RichHistory/RichHistoryCard.tsx | 45 ++++--- .../RichHistory/RichHistoryContainer.tsx | 34 ++--- .../RichHistory/RichHistoryQueriesTab.tsx | 14 +- .../RichHistory/RichHistoryStarredTab.tsx | 10 +- public/app/features/explore/state/actions.ts | 9 +- .../plugins/datasource/jaeger/datasource.ts | 4 + public/app/types/explore.ts | 2 +- public/sass/pages/_explore.scss | 2 +- 12 files changed, 237 insertions(+), 190 deletions(-) diff --git a/public/app/core/utils/richHistory.test.ts b/public/app/core/utils/richHistory.test.ts index a76cb2d3b26..5b4cb4a0a70 100644 --- a/public/app/core/utils/richHistory.test.ts +++ b/public/app/core/utils/richHistory.test.ts @@ -5,32 +5,37 @@ import { mapNumbertoTimeInSlider, createDateStringFromTs, createQueryHeading, - createDataQuery, deleteAllFromRichHistory, deleteQueryInRichHistory, } from './richHistory'; import store from 'app/core/store'; import { SortOrder } from './explore'; -import { dateTime } from '@grafana/data'; +import { dateTime, DataQuery } from '@grafana/data'; const mock: any = { - history: [ + storedHistory: [ { comment: '', datasourceId: 'datasource historyId', datasourceName: 'datasource history name', - queries: ['query1', 'query2'], + queries: [ + { expr: 'query1', refId: '1' }, + { expr: 'query2', refId: '2' }, + ], sessionName: '', starred: true, ts: 1, }, ], - comment: '', - datasourceId: 'datasourceId', - datasourceName: 'datasourceName', - queries: ['query3'], - sessionName: '', - starred: false, + testComment: '', + testDatasourceId: 'datasourceId', + testDatasourceName: 'datasourceName', + testQueries: [ + { expr: 'query3', refId: 'B' }, + { expr: 'query4', refId: 'C' }, + ], + testSessionName: '', + testStarred: false, }; const key = 'grafana.explore.richHistory'; @@ -43,27 +48,27 @@ describe('addToRichHistory', () => { const expectedResult = [ { - comment: mock.comment, - datasourceId: mock.datasourceId, - datasourceName: mock.datasourceName, - queries: mock.queries, - sessionName: mock.sessionName, - starred: mock.starred, + comment: mock.testComment, + datasourceId: mock.testDatasourceId, + datasourceName: mock.testDatasourceName, + queries: mock.testQueries, + sessionName: mock.testSessionName, + starred: mock.testStarred, ts: 2, }, - mock.history[0], + mock.storedHistory[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 + mock.storedHistory, + mock.testDatasourceId, + mock.testDatasourceName, + mock.testQueries, + mock.testStarred, + mock.testComment, + mock.testSessionName ); expect(newHistory).toEqual(expectedResult); }); @@ -72,13 +77,13 @@ describe('addToRichHistory', () => { Date.now = jest.fn(() => 2); addToRichHistory( - mock.history, - mock.datasourceId, - mock.datasourceName, - mock.queries, - mock.starred, - mock.comment, - mock.sessionName + mock.storedHistory, + mock.testDatasourceId, + mock.testDatasourceName, + mock.testQueries, + mock.testStarred, + mock.testComment, + mock.testSessionName ); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)).toMatchObject(expectedResult); @@ -87,27 +92,27 @@ describe('addToRichHistory', () => { 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 + mock.storedHistory, + mock.storedHistory[0].datasourceId, + mock.storedHistory[0].datasourceName, + [{ expr: 'query1', refId: 'A' } as DataQuery, { expr: 'query2', refId: 'B' } as DataQuery], + mock.testStarred, + mock.testComment, + mock.testSessionName ); - expect(newHistory).toEqual([mock.history[0]]); + expect(newHistory).toEqual([mock.storedHistory[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 + mock.storedHistory, + mock.storedHistory[0].datasourceId, + mock.storedHistory[0].datasourceName, + [{ expr: 'query1', refId: 'A' } as DataQuery, { expr: 'query2', refId: 'B' } as DataQuery], + mock.testStarred, + mock.testComment, + mock.testSessionName ); expect(store.exists(key)).toBeFalsy(); }); @@ -115,11 +120,11 @@ describe('addToRichHistory', () => { describe('updateStarredInRichHistory', () => { it('should update starred in query in history', () => { - const updatedStarred = updateStarredInRichHistory(mock.history, 1); + const updatedStarred = updateStarredInRichHistory(mock.storedHistory, 1); expect(updatedStarred[0].starred).toEqual(false); }); it('should update starred in localStorage', () => { - updateStarredInRichHistory(mock.history, 1); + updateStarredInRichHistory(mock.storedHistory, 1); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)[0].starred).toEqual(false); }); @@ -127,11 +132,11 @@ describe('updateStarredInRichHistory', () => { describe('updateCommentInRichHistory', () => { it('should update comment in query in history', () => { - const updatedComment = updateCommentInRichHistory(mock.history, 1, 'new comment'); + const updatedComment = updateCommentInRichHistory(mock.storedHistory, 1, 'new comment'); expect(updatedComment[0].comment).toEqual('new comment'); }); it('should update comment in localStorage', () => { - updateCommentInRichHistory(mock.history, 1, 'new comment'); + updateCommentInRichHistory(mock.storedHistory, 1, 'new comment'); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)[0].comment).toEqual('new comment'); }); @@ -139,11 +144,11 @@ describe('updateCommentInRichHistory', () => { describe('deleteQueryInRichHistory', () => { it('should delete query in query in history', () => { - const deletedHistory = deleteQueryInRichHistory(mock.history, 1); + const deletedHistory = deleteQueryInRichHistory(mock.storedHistory, 1); expect(deletedHistory).toEqual([]); }); it('should delete query in localStorage', () => { - deleteQueryInRichHistory(mock.history, 1); + deleteQueryInRichHistory(mock.storedHistory, 1); expect(store.exists(key)).toBeTruthy(); expect(store.getObject(key)).toEqual([]); }); @@ -166,19 +171,12 @@ describe('createDateStringFromTs', () => { 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.history[0].ts = 1 + -1 * dateTime().utcOffset() * 60 * 1000; - const heading = createQueryHeading(mock.history[0], SortOrder.Ascending); + 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.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' }); + const heading = createQueryHeading(mock.storedHistory[0], SortOrder.DatasourceAZ); + expect(heading).toEqual(mock.storedHistory[0].datasourceName); }); }); diff --git a/public/app/core/utils/richHistory.ts b/public/app/core/utils/richHistory.ts index 6a2748b56ad..4691c95a94a 100644 --- a/public/app/core/utils/richHistory.ts +++ b/public/app/core/utils/richHistory.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; // Services & Utils -import { DataQuery, ExploreMode, dateTime, AppEvents, urlUtil } from '@grafana/data'; +import { DataQuery, DataSourceApi, ExploreMode, dateTime, AppEvents, urlUtil } from '@grafana/data'; import appEvents from 'app/core/app_events'; import store from 'app/core/store'; import { serializeStateToUrlParam, SortOrder } from './explore'; @@ -29,39 +29,42 @@ export function addToRichHistory( richHistory: RichHistoryQuery[], datasourceId: string, datasourceName: string | null, - queries: string[], + queries: DataQuery[], starred: boolean, comment: string | null, sessionName: string ): any { const ts = Date.now(); - /* Save only queries, that are not falsy (e.g. empty strings, null) */ - const queriesToSave = queries.filter(expr => Boolean(expr)); - - const retentionPeriod = store.getObject(RICH_HISTORY_SETTING_KEYS.retentionPeriod, 7); + /* Save only queries, that are not falsy (e.g. empty object, null, ...) */ + const newQueriesToSave: DataQuery[] = queries && queries.filter(query => notEmptyQuery(query)); + const retentionPeriod: number = store.getObject(RICH_HISTORY_SETTING_KEYS.retentionPeriod, 7); const retentionPeriodLastTs = createRetentionPeriodBoundary(retentionPeriod, false); /* Keep only queries, that are within the selected retention period or that are starred. - * If no queries, initialize with exmpty array + * If no queries, initialize with empty array */ const queriesToKeep = richHistory.filter(q => q.ts > retentionPeriodLastTs || q.starred === true) || []; - if (queriesToSave.length > 0) { - if ( - /* Don't save duplicated queries for the same datasource */ + if (newQueriesToSave.length > 0) { + /* Compare queries of a new query and last saved queries. If they are the same, (except selected properties, + * which can be different) don't save it in rich history. + */ + const newQueriesToCompare = newQueriesToSave.map(q => _.omit(q, ['key', 'refId'])); + const lastQueriesToCompare = queriesToKeep.length > 0 && - JSON.stringify(queriesToSave) === JSON.stringify(queriesToKeep[0].queries) && - JSON.stringify(datasourceName) === JSON.stringify(queriesToKeep[0].datasourceName) - ) { + queriesToKeep[0].queries.map(q => { + return _.omit(q, ['key', 'refId']); + }); + + if (_.isEqual(newQueriesToCompare, lastQueriesToCompare)) { return richHistory; } let updatedHistory = [ - { queries: queriesToSave, ts, datasourceId, datasourceName, starred, comment, sessionName }, + { queries: newQueriesToSave, ts, datasourceId, datasourceName, starred, comment, sessionName }, ...queriesToKeep, ]; - /* If updatedHistory is succesfully saved, return it. Otherwise return not updated richHistory. */ try { store.setObject(RICH_HISTORY_KEY, updatedHistory); return updatedHistory; @@ -74,8 +77,10 @@ export function addToRichHistory( return richHistory; } -export function getRichHistory() { - return store.getObject(RICH_HISTORY_KEY, []); +export function getRichHistory(): RichHistoryQuery[] { + const richHistory: RichHistoryQuery[] = store.getObject(RICH_HISTORY_KEY, []); + const transformedRichHistory = migrateRichHistory(richHistory); + return transformedRichHistory; } export function deleteAllFromRichHistory() { @@ -168,14 +173,20 @@ export const copyStringToClipboard = (string: string) => { }; export const createUrlFromRichHistory = (query: RichHistoryQuery) => { - const queries = query.queries.map(query => ({ expr: query })); const exploreState: ExploreUrlState = { /* Default range, as we are not saving timerange in rich history */ range: { from: 'now-1h', to: 'now' }, datasource: query.datasourceName, - queries, - /* Default mode. In the future, we can also save the query mode */ - mode: query.datasourceId === 'loki' ? ExploreMode.Logs : ExploreMode.Metrics, + queries: query.queries, + /* Default mode is metrics. Exceptions are Loki (logs) and Jaeger (tracing) data sources. + * In the future, we can remove this as we are working on metrics & logs logic. + **/ + mode: + query.datasourceId === 'loki' + ? ExploreMode.Logs + : query.datasourceId === 'jaeger' + ? ExploreMode.Tracing + : ExploreMode.Metrics, ui: { showingGraph: true, showingLogs: true, @@ -230,7 +241,12 @@ export function createDateStringFromTs(ts: number) { } export function getQueryDisplayText(query: DataQuery): string { - return JSON.stringify(query); + /* If datasource doesn't have getQueryDisplayText, create query display text by + * stringifying query that was stripped of key, refId and datasource for nicer + * formatting and improved readability + */ + const strippedQuery = _.omit(query, ['key', 'refId', 'datasource']); + return JSON.stringify(strippedQuery); } export function createQueryHeading(query: RichHistoryQuery, sortOrder: SortOrder) { @@ -243,23 +259,15 @@ export function createQueryHeading(query: RichHistoryQuery, sortOrder: SortOrder return heading; } -export function isParsable(string: string) { - try { - JSON.parse(string); - } catch (e) { - return false; +export function createQueryText(query: DataQuery, queryDsInstance: DataSourceApi) { + /* query DatasourceInstance is necessary because we use its getQueryDisplayText method + * to format query text + */ + if (queryDsInstance?.getQueryDisplayText) { + return queryDsInstance.getQueryDisplayText(query); } - return true; -} -export function createDataQuery(query: RichHistoryQuery, queryString: string, index: number) { - let dataQuery; - const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - isParsable(queryString) - ? (dataQuery = JSON.parse(queryString)) - : (dataQuery = { expr: queryString, refId: letters[index], datasource: query.datasourceName }); - - return dataQuery; + return getQueryDisplayText(query); } export function mapQueriesToHeadings(query: RichHistoryQuery[], sortOrder: SortOrder) { @@ -304,3 +312,46 @@ export function createDatasourcesList(queriesDatasources: string[]) { }); return datasources; } + +export function notEmptyQuery(query: DataQuery) { + /* Check if query has any other properties besides key, refId and datasource. + * If not, then we consider it empty query. + */ + const strippedQuery = _.omit(query, ['key', 'refId', 'datasource']); + const queryKeys = Object.keys(strippedQuery); + + if (queryKeys.length > 0) { + return true; + } + + return false; +} + +/* These functions are created to migrate string queries (from 6.7 release) to DataQueries. They can be removed after 7.1 release. */ +function migrateRichHistory(richHistory: RichHistoryQuery[]) { + const transformedRichHistory = richHistory.map(query => { + const transformedQueries: DataQuery[] = query.queries.map((q, index) => createDataQuery(query, q, index)); + return { ...query, queries: transformedQueries }; + }); + + return transformedRichHistory; +} + +function createDataQuery(query: RichHistoryQuery, individualQuery: DataQuery | string, index: number) { + const letters = 'ABCDEFGHIJKLMNOPQRSTUVXYZ'; + if (typeof individualQuery === 'object') { + return individualQuery; + } else if (isParsable(individualQuery)) { + return JSON.parse(individualQuery); + } + return { expr: individualQuery, refId: letters[index] }; +} + +function isParsable(string: string) { + try { + JSON.parse(string); + } catch (e) { + return false; + } + return true; +} diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index fa0892c7c9a..0c9e1a13859 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -24,8 +24,8 @@ export enum Tabs { } export const sortOrderOptions = [ - { label: 'Time ascending', value: SortOrder.Ascending }, - { label: 'Time descending', value: SortOrder.Descending }, + { label: 'Newest first', value: SortOrder.Descending }, + { label: 'Oldest first', value: SortOrder.Ascending }, { label: 'Data source A-Z', value: SortOrder.DatasourceAZ }, { label: 'Data source Z-A', value: SortOrder.DatasourceZA }, ]; @@ -50,15 +50,13 @@ interface RichHistoryState { } const getStyles = stylesFactory((theme: GrafanaTheme) => { - const borderColor = theme.isLight ? theme.palette.gray5 : theme.palette.dark6; - const tabContentBg = theme.colors.bodyBg; return { container: css` height: 100%; `, tabContent: css` padding: ${theme.spacing.md}; - background-color: ${tabContentBg}; + background-color: ${theme.colors.bodyBg}; `, close: css` position: absolute; @@ -69,11 +67,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { `, tabs: css` padding-top: ${theme.spacing.sm}; - border-color: ${borderColor}; + border-color: ${theme.colors.formInputBorder}; ul { margin-left: ${theme.spacing.md}; } `, + scrollbar: css` + min-height: 100% !important; + background-color: ${theme.colors.panelBg}; + `, }; }); @@ -228,11 +230,7 @@ class UnThemedRichHistory extends PureComponent - + {tabs.find(t => t.value === activeTab)?.content} diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx index ebbb54d2e0b..25b12b9580b 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import { RichHistoryCard, Props } from './RichHistoryCard'; import { ExploreId } from '../../../types/explore'; -import { DataSourceApi } from '@grafana/data'; +import { DataSourceApi, DataQuery } from '@grafana/data'; const setup = (propOverrides?: Partial) => { const props: Props = { @@ -12,7 +12,11 @@ const setup = (propOverrides?: Partial) => { datasourceId: 'datasource 1', starred: false, comment: '', - queries: ['query1', 'query2', 'query3'], + queries: [ + { expr: 'query1', refId: 'A' } as DataQuery, + { expr: 'query2', refId: 'B' } as DataQuery, + { expr: 'query3', refId: 'C' } as DataQuery, + ], sessionName: '', }, dsImg: '/app/img', @@ -26,7 +30,7 @@ const setup = (propOverrides?: Partial) => { Object.assign(props, propOverrides); - const wrapper = mount(); + const wrapper = shallow(); return wrapper; }; @@ -36,7 +40,11 @@ const starredQueryWithComment = { datasourceId: 'datasource 1', starred: true, comment: 'test comment', - queries: ['query1', 'query2', 'query3'], + queries: [ + { query: 'query1', refId: 'A' }, + { query: 'query2', refId: 'B' }, + { query: 'query3', refId: 'C' }, + ], sessionName: '', }; @@ -49,19 +57,19 @@ describe('RichHistoryCard', () => { .find({ 'aria-label': 'Query text' }) .at(0) .text() - ).toEqual('query1'); + ).toEqual('{"expr":"query1"}'); expect( wrapper .find({ 'aria-label': 'Query text' }) .at(1) .text() - ).toEqual('query2'); + ).toEqual('{"expr":"query2"}'); expect( wrapper .find({ 'aria-label': 'Query text' }) .at(2) .text() - ).toEqual('query3'); + ).toEqual('{"expr":"query3"}'); }); it('should render data source icon', () => { const wrapper = setup(); @@ -79,29 +87,29 @@ describe('RichHistoryCard', () => { describe('commenting', () => { it('should render comment, if comment present', () => { const wrapper = setup({ query: starredQueryWithComment }); - expect(wrapper.find({ 'aria-label': 'Query comment' }).hostNodes()).toHaveLength(1); + 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' }).hostNodes()).toHaveLength(1); - expect(wrapper.find({ title: 'Add comment' }).hostNodes()).toHaveLength(0); + 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' }).hostNodes()).toHaveLength(1); - expect(wrapper.find({ title: 'Edit comment' }).hostNodes()).toHaveLength(0); + 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' }).hostNodes()).toHaveLength(1); + expect(wrapper.find({ title: 'Star query' })).toHaveLength(1); }); it('should have title "Unstar query", if not starred', () => { const wrapper = setup({ query: starredQueryWithComment }); - expect(wrapper.find({ title: 'Unstar query' }).hostNodes()).toHaveLength(1); + expect(wrapper.find({ title: 'Unstar query' })).toHaveLength(1); }); }); }); diff --git a/public/app/features/explore/RichHistory/RichHistoryCard.tsx b/public/app/features/explore/RichHistory/RichHistoryCard.tsx index e8f2a20a47f..43c689a4a78 100644 --- a/public/app/features/explore/RichHistory/RichHistoryCard.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryCard.tsx @@ -1,12 +1,12 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; import { css, cx } from 'emotion'; import { stylesFactory, useTheme, TextArea, Button, IconButton } from '@grafana/ui'; - +import { getDataSourceSrv } from '@grafana/runtime'; import { GrafanaTheme, AppEvents, DataSourceApi } from '@grafana/data'; import { RichHistoryQuery, ExploreId } from 'app/types/explore'; -import { copyStringToClipboard, createUrlFromRichHistory, createDataQuery } from 'app/core/utils/richHistory'; +import { copyStringToClipboard, createUrlFromRichHistory, createQueryText } from 'app/core/utils/richHistory'; import appEvents from 'app/core/app_events'; import { StoreState } from 'app/types'; @@ -27,8 +27,6 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isRemoved: boolean) => { const rigtColumnWidth = '240px'; const rigtColumnContentWidth = '170px'; - const borderColor = theme.isLight ? theme.palette.gray5 : theme.palette.gray25; - /* If datasource was removed, card will have inactive color */ const cardColor = theme.isLight ? isRemoved @@ -42,7 +40,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isRemoved: boolean) => { queryCard: css` display: flex; flex-direction: column; - border: 1px solid ${borderColor}; + border: 1px solid ${theme.colors.formInputBorder}; margin: ${theme.spacing.sm} 0; background-color: ${cardColor}; border-radius: ${theme.border.radius.sm}; @@ -57,7 +55,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isRemoved: boolean) => { padding: ${theme.spacing.sm}; border-bottom: none; :first-of-type { - border-bottom: 1px solid ${borderColor}; + border-bottom: 1px solid ${theme.colors.formInputBorder}; padding: ${theme.spacing.xs} ${theme.spacing.sm}; } img { @@ -86,7 +84,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isRemoved: boolean) => { width: calc(100% - ${rigtColumnWidth}); `, queryRow: css` - border-top: 1px solid ${borderColor}; + border-top: 1px solid ${theme.colors.formInputBorder}; word-break: break-all; padding: 4px 2px; :first-child { @@ -110,7 +108,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isRemoved: boolean) => { } `, textArea: css` - border: 1px solid ${borderColor}; + border: 1px solid ${theme.colors.formInputBorder}; background: inherit; color: inherit; width: 100%; @@ -125,7 +123,8 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isRemoved: boolean) => { justify-content: flex-end; button { height: auto; - padding: ${theme.spacing.sm} ${theme.spacing.md}; + padding: ${theme.spacing.xs} ${theme.spacing.md}; + line-height: 1.4; span { white-space: normal !important; } @@ -147,24 +146,33 @@ export function RichHistoryCard(props: Props) { } = props; const [activeUpdateComment, setActiveUpdateComment] = useState(false); const [comment, setComment] = useState(query.comment); + const [queryDsInstance, setQueryDsInstance] = useState(undefined); + + useEffect(() => { + const getQueryDsInstance = async () => { + const ds = await getDataSourceSrv().get(query.datasourceName); + setQueryDsInstance(ds); + }; + + getQueryDsInstance(); + }, [query.datasourceName]); - const toggleActiveUpdateComment = () => setActiveUpdateComment(!activeUpdateComment); const theme = useTheme(); const styles = getStyles(theme, isRemoved); const onRunQuery = async () => { - const dataQueries = query.queries.map((q, i) => createDataQuery(query, q, i)); + const queriesToRun = query.queries; if (query.datasourceName !== datasourceInstance?.name) { await changeDatasource(exploreId, query.datasourceName); - setQueries(exploreId, dataQueries); + setQueries(exploreId, queriesToRun); } else { - setQueries(exploreId, dataQueries); + setQueries(exploreId, queriesToRun); } }; const onCopyQuery = () => { - const queries = query.queries.join('\n\n'); - copyStringToClipboard(queries); + const queriesToCopy = query.queries.map(q => createQueryText(q, queryDsInstance)).join('\n'); + copyStringToClipboard(queriesToCopy); appEvents.emit(AppEvents.alertSuccess, ['Query copied to clipboard']); }; @@ -183,6 +191,8 @@ export function RichHistoryCard(props: Props) { updateRichHistory(query.ts, 'starred'); }; + const toggleActiveUpdateComment = () => setActiveUpdateComment(!activeUpdateComment); + const onUpdateComment = () => { updateRichHistory(query.ts, 'comment', comment); toggleActiveUpdateComment(); @@ -243,9 +253,10 @@ export function RichHistoryCard(props: Props) {
{query.queries.map((q, i) => { + const queryText = createQueryText(q, queryDsInstance); return (
- {q} + {queryText}
); })} diff --git a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx index 69a061ada8d..3687a850dcd 100644 --- a/public/app/features/explore/RichHistory/RichHistoryContainer.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryContainer.tsx @@ -22,22 +22,19 @@ import { RichHistory, Tabs } from './RichHistory'; import { deleteRichHistory } from '../state/actions'; const getStyles = stylesFactory((theme: GrafanaTheme) => { - const containerBackground = theme.isLight ? theme.palette.gray95 : theme.palette.gray15; - const containerBorderColor = theme.isLight ? theme.palette.gray5 : theme.palette.dark6; - const handleBackground = theme.isLight ? theme.palette.white : theme.palette.gray15; - const handleDots = theme.isLight ? theme.palette.gray85 : theme.palette.gray33; - const handleBackgroundHover = theme.isLight ? theme.palette.gray85 : theme.palette.gray33; - const handleDotsHover = theme.isLight ? theme.palette.gray70 : theme.palette.dark7; + const shadowColor = theme.isLight ? theme.palette.gray4 : theme.palette.black; return { container: css` position: fixed !important; bottom: 0; - background: ${containerBackground}; - border-top: 1px solid ${containerBorderColor}; + background: ${theme.colors.pageHeaderBg}; + border-top: 1px solid ${theme.colors.formInputBorder}; margin: 0px; margin-right: -${theme.spacing.md}; margin-left: -${theme.spacing.md}; + box-shadow: 0 0 4px ${shadowColor}; + z-index: ${theme.zIndex.sidemenu}; `, drawerActive: css` opacity: 1; @@ -48,30 +45,17 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { transform: translateY(400px); `, rzHandle: css` - background: ${handleBackground}; + background: ${theme.colors.formInputBorder}; transition: 0.3s background ease-in-out; position: relative; width: 200px !important; + height: 7px !important; left: calc(50% - 100px) !important; + top: -4px !important; cursor: grab; border-radius: 4px; - &:hover { - background-color: ${handleBackgroundHover}; - - &:after { - border-color: ${handleDotsHover}; - } - } - - &:after { - content: ''; - display: block; - height: 2px; - position: relative; - top: 4px; - border-top: 4px dotted ${handleDots}; - margin: 0 4px; + background: ${theme.colors.formInputBorderHover}; } `, }; diff --git a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx index 152ee44f02c..d7cfca19b1f 100644 --- a/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryQueriesTab.tsx @@ -21,8 +21,7 @@ import { // Components import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; -import { LegacyForms, Slider } from '@grafana/ui'; -const { Select } = LegacyForms; +import { Slider, Select } from '@grafana/ui'; export interface Props { queries: RichHistoryQuery[]; @@ -60,12 +59,12 @@ const getStyles = stylesFactory((theme: GrafanaTheme, height: number) => { width: calc(${cardWidth}); `, containerSlider: css` - width: 127px; + width: 129px; margin-right: ${theme.spacing.sm}; .slider { bottom: 10px; height: ${sliderHeight}; - width: 127px; + width: 129px; padding: ${theme.spacing.sm} 0; } `, @@ -141,9 +140,10 @@ export function RichHistoryQueriesTab(props: Props) { const listOfDatasources = createDatasourcesList(datasourcesRetrievedFromQueryHistory); const listOfDatasourceFilters = datasourceFilters?.map(d => d.value); - const filteredQueriesByDatasource = datasourceFilters - ? queries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName)) - : queries; + const filteredQueriesByDatasource = + listOfDatasourceFilters && listOfDatasourceFilters?.length > 0 + ? queries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName)) + : queries; const sortedQueries = sortQueries(filteredQueriesByDatasource, sortOrder); const queriesWithinSelectedTimeline = sortedQueries?.filter( diff --git a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx index c09974970e3..aafb94f8ffa 100644 --- a/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx @@ -15,8 +15,7 @@ import { sortQueries, createDatasourcesList } from '../../../core/utils/richHist // Components import RichHistoryCard from './RichHistoryCard'; import { sortOrderOptions } from './RichHistory'; -import { LegacyForms } from '@grafana/ui'; -const { Select } = LegacyForms; +import { Select } from '@grafana/ui'; export interface Props { queries: RichHistoryQuery[]; @@ -87,9 +86,10 @@ export function RichHistoryStarredTab(props: Props) { const listOfDatasourceFilters = datasourceFilters?.map(d => d.value); const starredQueries = queries.filter(q => q.starred === true); - const starredQueriesFilteredByDatasource = datasourceFilters - ? starredQueries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName)) - : starredQueries; + const starredQueriesFilteredByDatasource = + listOfDatasourceFilters && listOfDatasourceFilters?.length > 0 + ? starredQueries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName)) + : starredQueries; const sortedStarredQueries = sortQueries(starredQueriesFilteredByDatasource, sortOrder); diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index e4f903f78ea..c59983991b5 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -44,7 +44,6 @@ import { updateStarredInRichHistory, updateCommentInRichHistory, deleteQueryInRichHistory, - getQueryDisplayText, getRichHistory, } from 'app/core/utils/richHistory'; // Types @@ -487,17 +486,11 @@ export const runQueries = (exploreId: ExploreId): ThunkResult => { if (!data.error && firstResponse) { // Side-effect: Saving history in localstorage const nextHistory = updateHistory(history, datasourceId, queries); - const arrayOfStringifiedQueries = queries.map(query => - datasourceInstance?.getQueryDisplayText - ? datasourceInstance.getQueryDisplayText(query) - : getQueryDisplayText(query) - ); - const nextRichHistory = addToRichHistory( richHistory || [], datasourceId, datasourceName, - arrayOfStringifiedQueries, + queries, false, '', '' diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index 9dde828033e..9aac9ebac40 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -83,6 +83,10 @@ export class JaegerDatasource extends DataSourceApi { }; } + getQueryDisplayText(query: JaegerQuery) { + return query.query; + } + private _request(apiUrl: string, data?: any, options?: DatasourceRequestOptions): Observable> { // Hack for proxying metadata requests const baseUrl = `/api/datasources/proxy/${this.instanceSettings.id}`; diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index fdaa14f1c8a..3640c6875de 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -241,7 +241,7 @@ export type RichHistoryQuery = { datasourceId: string; starred: boolean; comment: string; - queries: string[]; + queries: DataQuery[]; sessionName: string; timeRange?: string; }; diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index a29ac99513a..7e44d0dcef6 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -20,7 +20,7 @@ .explore-active-button { box-shadow: $btn-active-box-shadow; - border-color: $orange-dark; + border: 1px solid $orange-dark; background-image: none; background-color: transparent; color: $orange-dark !important;