mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Query History: Implement RemoteStorage methods: settings (#49320)
* Implement methods to get an update user preferences * Update integration test * Update label * Remove unused type * Simplify async code
This commit is contained in:
parent
34d77fd584
commit
16a948fc88
@ -22,18 +22,16 @@ import { PreferencesService } from 'app/core/services/PreferencesService';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types';
|
||||
|
||||
import { UserPreferencesDTO } from '../../../types';
|
||||
|
||||
export interface Props {
|
||||
resourceUri: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
homeDashboardId: number;
|
||||
theme: string;
|
||||
timezone: string;
|
||||
weekStart: string;
|
||||
export type State = UserPreferencesDTO & {
|
||||
dashboards: DashboardSearchHit[];
|
||||
}
|
||||
};
|
||||
|
||||
const themes: SelectableValue[] = [
|
||||
{ value: '', label: t({ id: 'shared-preferences.theme.default-label', message: 'Default' }) },
|
||||
@ -54,6 +52,7 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
timezone: '',
|
||||
weekStart: '',
|
||||
dashboards: [],
|
||||
queryHistory: { homeTab: '' },
|
||||
};
|
||||
}
|
||||
|
||||
@ -90,12 +89,13 @@ export class SharedPreferences extends PureComponent<Props, State> {
|
||||
timezone: prefs.timezone,
|
||||
weekStart: prefs.weekStart,
|
||||
dashboards: [defaultDashboardHit, ...dashboards],
|
||||
queryHistory: prefs.queryHistory,
|
||||
});
|
||||
}
|
||||
|
||||
onSubmitForm = async () => {
|
||||
const { homeDashboardId, theme, timezone, weekStart } = this.state;
|
||||
await this.service.update({ homeDashboardId, theme, timezone, weekStart });
|
||||
const { homeDashboardId, theme, timezone, weekStart, queryHistory } = this.state;
|
||||
await this.service.update({ homeDashboardId, theme, timezone, weekStart, queryHistory });
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { DatasourceSrv } from '../../features/plugins/datasource_srv';
|
||||
import { RichHistoryQuery } from '../../types';
|
||||
import { RichHistoryQuery, UserPreferencesDTO } from '../../types';
|
||||
import { SortOrder } from '../utils/richHistoryTypes';
|
||||
|
||||
import RichHistoryRemoteStorage, { RichHistoryRemoteStorageDTO } from './RichHistoryRemoteStorage';
|
||||
@ -26,6 +26,16 @@ jest.mock('@grafana/runtime', () => ({
|
||||
getDataSourceSrv: () => dsMock,
|
||||
}));
|
||||
|
||||
const preferencesServiceMock = {
|
||||
patch: jest.fn(),
|
||||
load: jest.fn(),
|
||||
};
|
||||
jest.mock('../services/PreferencesService', () => ({
|
||||
PreferencesService: function () {
|
||||
return preferencesServiceMock;
|
||||
},
|
||||
}));
|
||||
|
||||
describe('RichHistoryRemoteStorage', () => {
|
||||
let storage: RichHistoryRemoteStorage;
|
||||
|
||||
@ -91,6 +101,58 @@ describe('RichHistoryRemoteStorage', () => {
|
||||
expect(items).toMatchObject([richHistoryQuery]);
|
||||
});
|
||||
|
||||
it('read starred home tab preferences', async () => {
|
||||
preferencesServiceMock.load.mockResolvedValue({
|
||||
queryHistory: {
|
||||
homeTab: 'starred',
|
||||
},
|
||||
} as UserPreferencesDTO);
|
||||
const settings = await storage.getSettings();
|
||||
expect(settings).toMatchObject({
|
||||
activeDatasourceOnly: false,
|
||||
lastUsedDatasourceFilters: undefined,
|
||||
retentionPeriod: 14,
|
||||
starredTabAsFirstTab: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('uses default home tab preferences', async () => {
|
||||
preferencesServiceMock.load.mockResolvedValue({
|
||||
queryHistory: {
|
||||
homeTab: '',
|
||||
},
|
||||
} as UserPreferencesDTO);
|
||||
const settings = await storage.getSettings();
|
||||
expect(settings).toMatchObject({
|
||||
activeDatasourceOnly: false,
|
||||
lastUsedDatasourceFilters: undefined,
|
||||
retentionPeriod: 14,
|
||||
starredTabAsFirstTab: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('updates user settings', async () => {
|
||||
await storage.updateSettings({
|
||||
activeDatasourceOnly: false,
|
||||
lastUsedDatasourceFilters: undefined,
|
||||
retentionPeriod: 14,
|
||||
starredTabAsFirstTab: false,
|
||||
});
|
||||
expect(preferencesServiceMock.patch).toBeCalledWith({
|
||||
queryHistory: { homeTab: 'query' },
|
||||
} as Partial<UserPreferencesDTO>);
|
||||
|
||||
await storage.updateSettings({
|
||||
activeDatasourceOnly: false,
|
||||
lastUsedDatasourceFilters: undefined,
|
||||
retentionPeriod: 14,
|
||||
starredTabAsFirstTab: true,
|
||||
});
|
||||
expect(preferencesServiceMock.patch).toBeCalledWith({
|
||||
queryHistory: { homeTab: 'starred' },
|
||||
} as Partial<UserPreferencesDTO>);
|
||||
});
|
||||
|
||||
it('migrates provided rich history items', async () => {
|
||||
const { richHistoryQuery, dto } = setup();
|
||||
fetchMock.mockReturnValue(of({}));
|
||||
|
@ -4,6 +4,7 @@ import { getBackendSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { RichHistoryQuery } from 'app/types/explore';
|
||||
|
||||
import { DataQuery } from '../../../../packages/grafana-data';
|
||||
import { PreferencesService } from '../services/PreferencesService';
|
||||
import { RichHistorySearchFilters, RichHistorySettings, SortOrder } from '../utils/richHistoryTypes';
|
||||
|
||||
import RichHistoryStorage, { RichHistoryStorageWarningDetails } from './RichHistoryStorage';
|
||||
@ -37,6 +38,12 @@ type RichHistoryRemoteStorageResultsPayloadDTO = {
|
||||
};
|
||||
|
||||
export default class RichHistoryRemoteStorage implements RichHistoryStorage {
|
||||
private readonly preferenceService: PreferencesService;
|
||||
|
||||
constructor() {
|
||||
this.preferenceService = new PreferencesService('user');
|
||||
}
|
||||
|
||||
async addToRichHistory(
|
||||
newRichHistoryQuery: Omit<RichHistoryQuery, 'id' | 'createdAt'>
|
||||
): Promise<{ warning?: RichHistoryStorageWarningDetails; richHistoryQuery: RichHistoryQuery }> {
|
||||
@ -71,11 +78,12 @@ export default class RichHistoryRemoteStorage implements RichHistoryStorage {
|
||||
}
|
||||
|
||||
async getSettings(): Promise<RichHistorySettings> {
|
||||
const preferences = await this.preferenceService.load();
|
||||
return {
|
||||
activeDatasourceOnly: false,
|
||||
lastUsedDatasourceFilters: undefined,
|
||||
retentionPeriod: 14,
|
||||
starredTabAsFirstTab: false,
|
||||
starredTabAsFirstTab: preferences.queryHistory?.homeTab === 'starred',
|
||||
};
|
||||
}
|
||||
|
||||
@ -83,8 +91,12 @@ export default class RichHistoryRemoteStorage implements RichHistoryStorage {
|
||||
throw new Error('not supported yet');
|
||||
}
|
||||
|
||||
async updateSettings(settings: RichHistorySettings): Promise<void> {
|
||||
throw new Error('not supported yet');
|
||||
updateSettings(settings: RichHistorySettings): Promise<void> {
|
||||
return this.preferenceService.patch({
|
||||
queryHistory: {
|
||||
homeTab: settings.starredTabAsFirstTab ? 'starred' : 'query',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async updateStarred(id: string, starred: boolean): Promise<RichHistoryQuery> {
|
||||
|
@ -16,6 +16,9 @@ export const getRichHistoryStorage = (): RichHistoryStorage => {
|
||||
interface RichHistorySupportedFeatures {
|
||||
availableFilters: SortOrder[];
|
||||
lastUsedDataSourcesAvailable: boolean;
|
||||
clearHistory: boolean;
|
||||
onlyActiveDataSource: boolean;
|
||||
changeRetention: boolean;
|
||||
}
|
||||
|
||||
export const supportedFeatures = (): RichHistorySupportedFeatures => {
|
||||
@ -23,9 +26,15 @@ export const supportedFeatures = (): RichHistorySupportedFeatures => {
|
||||
? {
|
||||
availableFilters: [SortOrder.Descending, SortOrder.Ascending],
|
||||
lastUsedDataSourcesAvailable: false,
|
||||
clearHistory: false,
|
||||
onlyActiveDataSource: false,
|
||||
changeRetention: false,
|
||||
}
|
||||
: {
|
||||
availableFilters: [SortOrder.Descending, SortOrder.Ascending, SortOrder.DatasourceAZ, SortOrder.DatasourceZA],
|
||||
lastUsedDataSourcesAvailable: true,
|
||||
clearHistory: true,
|
||||
onlyActiveDataSource: true,
|
||||
changeRetention: true,
|
||||
};
|
||||
};
|
||||
|
@ -5,10 +5,20 @@ import { backendSrv } from './backend_srv';
|
||||
export class PreferencesService {
|
||||
constructor(private resourceUri: string) {}
|
||||
|
||||
/**
|
||||
* Overrides all preferences
|
||||
*/
|
||||
update(preferences: UserPreferencesDTO): Promise<any> {
|
||||
return backendSrv.put(`/api/${this.resourceUri}/preferences`, preferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates only provided preferences
|
||||
*/
|
||||
patch(preferences: Partial<UserPreferencesDTO>): Promise<any> {
|
||||
return backendSrv.patch(`/api/${this.resourceUri}/preferences`, preferences);
|
||||
}
|
||||
|
||||
load(): Promise<UserPreferencesDTO> {
|
||||
return backendSrv.get<UserPreferencesDTO>(`/api/${this.resourceUri}/preferences`);
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||
import { stylesFactory, useTheme, Select, Button, Field, InlineField, InlineSwitch } from '@grafana/ui';
|
||||
import { stylesFactory, useTheme, Select, Button, Field, InlineField, InlineSwitch, Alert } from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
||||
import { MAX_HISTORY_ITEMS } from 'app/core/history/RichHistoryLocalStorage';
|
||||
import { dispatch } from 'app/store/store';
|
||||
|
||||
import { supportedFeatures } from '../../../core/history/richHistoryStorageProvider';
|
||||
import { ShowConfirmModalEvent } from '../../../types/events';
|
||||
|
||||
export interface RichHistorySettingsProps {
|
||||
@ -73,15 +74,21 @@ export function RichHistorySettingsTab(props: RichHistorySettingsProps) {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Field
|
||||
label="History time span"
|
||||
description={`Select the period of time for which Grafana will save your query history. Up to ${MAX_HISTORY_ITEMS} entries will be stored.`}
|
||||
className="space-between"
|
||||
>
|
||||
<div className={styles.input}>
|
||||
<Select value={selectedOption} options={retentionPeriodOptions} onChange={onChangeRetentionPeriod}></Select>
|
||||
</div>
|
||||
</Field>
|
||||
{supportedFeatures().changeRetention ? (
|
||||
<Field
|
||||
label="History time span"
|
||||
description={`Select the period of time for which Grafana will save your query history. Up to ${MAX_HISTORY_ITEMS} entries will be stored.`}
|
||||
className="space-between"
|
||||
>
|
||||
<div className={styles.input}>
|
||||
<Select value={selectedOption} options={retentionPeriodOptions} onChange={onChangeRetentionPeriod}></Select>
|
||||
</div>
|
||||
</Field>
|
||||
) : (
|
||||
<Alert severity="info" title="History time span">
|
||||
Grafana will keep entries up to {selectedOption?.label}.
|
||||
</Alert>
|
||||
)}
|
||||
<InlineField label="Change the default active tab from “Query history” to “Starred”" className="space-between">
|
||||
<InlineSwitch
|
||||
id="explore-query-history-settings-default-active-tab"
|
||||
@ -89,30 +96,36 @@ export function RichHistorySettingsTab(props: RichHistorySettingsProps) {
|
||||
onChange={toggleStarredTabAsFirstTab}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField label="Only show queries for data source currently active in Explore" className="space-between">
|
||||
<InlineSwitch
|
||||
id="explore-query-history-settings-data-source-behavior"
|
||||
value={activeDatasourceOnly}
|
||||
onChange={toggleactiveDatasourceOnly}
|
||||
/>
|
||||
</InlineField>
|
||||
<div
|
||||
className={css`
|
||||
font-weight: ${theme.typography.weight.bold};
|
||||
`}
|
||||
>
|
||||
Clear query history
|
||||
</div>
|
||||
<div
|
||||
className={css`
|
||||
margin-bottom: ${theme.spacing.sm};
|
||||
`}
|
||||
>
|
||||
Delete all of your query history, permanently.
|
||||
</div>
|
||||
<Button variant="destructive" onClick={onDelete}>
|
||||
Clear query history
|
||||
</Button>
|
||||
{supportedFeatures().onlyActiveDataSource && (
|
||||
<InlineField label="Only show queries for data source currently active in Explore" className="space-between">
|
||||
<InlineSwitch
|
||||
id="explore-query-history-settings-data-source-behavior"
|
||||
value={activeDatasourceOnly}
|
||||
onChange={toggleactiveDatasourceOnly}
|
||||
/>
|
||||
</InlineField>
|
||||
)}
|
||||
{supportedFeatures().clearHistory && (
|
||||
<div>
|
||||
<div
|
||||
className={css`
|
||||
font-weight: ${theme.typography.weight.bold};
|
||||
`}
|
||||
>
|
||||
Clear query history
|
||||
</div>
|
||||
<div
|
||||
className={css`
|
||||
margin-bottom: ${theme.spacing.sm};
|
||||
`}
|
||||
>
|
||||
Delete all of your query history, permanently.
|
||||
</div>
|
||||
<Button variant="destructive" onClick={onDelete}>
|
||||
Clear query history
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -36,6 +36,19 @@ jest.mock('@grafana/runtime', () => ({
|
||||
getBackendSrv: () => ({ fetch: fetchMock, post: postMock, get: getMock }),
|
||||
}));
|
||||
|
||||
jest.mock('app/core/services/PreferencesService', () => ({
|
||||
PreferencesService: function () {
|
||||
return {
|
||||
patch: jest.fn(),
|
||||
load: jest.fn().mockResolvedValue({
|
||||
queryHistory: {
|
||||
homeTab: 'query',
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-virtualized-auto-sizer', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
|
@ -5,4 +5,7 @@ export interface UserPreferencesDTO {
|
||||
weekStart: string;
|
||||
homeDashboardId: number;
|
||||
theme: string;
|
||||
queryHistory: {
|
||||
homeTab: '' | 'query' | 'starred';
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user