Commit 66e5a1c0 by Ivana Huckova Committed by GitHub

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
parent 8d56f874
......@@ -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);
});
});
......@@ -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;
}
......@@ -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<RichHistoryProps, RichHistorySta
))}
<IconButton className={styles.close} onClick={onClose} name="times" title="Close query history" />
</TabsBar>
<CustomScrollbar
className={css`
min-height: 100% !important;
`}
>
<CustomScrollbar className={styles.scrollbar}>
<TabContent className={styles.tabContent}>{tabs.find(t => t.value === activeTab)?.content}</TabContent>
</CustomScrollbar>
</div>
......
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<Props>) => {
const props: Props = {
......@@ -12,7 +12,11 @@ const setup = (propOverrides?: Partial<Props>) => {
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<Props>) => {
Object.assign(props, propOverrides);
const wrapper = mount(<RichHistoryCard {...props} />);
const wrapper = shallow(<RichHistoryCard {...props} />);
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);
});
});
});
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<string | undefined>(query.comment);
const [queryDsInstance, setQueryDsInstance] = useState<DataSourceApi | undefined>(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) {
<div className={cx(styles.cardRow)}>
<div className={styles.queryContainer}>
{query.queries.map((q, i) => {
const queryText = createQueryText(q, queryDsInstance);
return (
<div aria-label="Query text" key={`${q}-${i}`} className={styles.queryRow}>
{q}
{queryText}
</div>
);
})}
......
......@@ -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};
}
`,
};
......
......@@ -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,7 +140,8 @@ export function RichHistoryQueriesTab(props: Props) {
const listOfDatasources = createDatasourcesList(datasourcesRetrievedFromQueryHistory);
const listOfDatasourceFilters = datasourceFilters?.map(d => d.value);
const filteredQueriesByDatasource = datasourceFilters
const filteredQueriesByDatasource =
listOfDatasourceFilters && listOfDatasourceFilters?.length > 0
? queries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName))
: queries;
......
......@@ -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,7 +86,8 @@ export function RichHistoryStarredTab(props: Props) {
const listOfDatasourceFilters = datasourceFilters?.map(d => d.value);
const starredQueries = queries.filter(q => q.starred === true);
const starredQueriesFilteredByDatasource = datasourceFilters
const starredQueriesFilteredByDatasource =
listOfDatasourceFilters && listOfDatasourceFilters?.length > 0
? starredQueries?.filter(q => listOfDatasourceFilters?.includes(q.datasourceName))
: starredQueries;
......
......@@ -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<void> => {
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,
'',
''
......
......@@ -83,6 +83,10 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
};
}
getQueryDisplayText(query: JaegerQuery) {
return query.query;
}
private _request(apiUrl: string, data?: any, options?: DatasourceRequestOptions): Observable<Record<string, any>> {
// Hack for proxying metadata requests
const baseUrl = `/api/datasources/proxy/${this.instanceSettings.id}`;
......
......@@ -241,7 +241,7 @@ export type RichHistoryQuery = {
datasourceId: string;
starred: boolean;
comment: string;
queries: string[];
queries: DataQuery[];
sessionName: string;
timeRange?: string;
};
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment