grafana/public/app/core/utils/richHistory.ts

242 lines
7.5 KiB
TypeScript

import { omit } from 'lodash';
import { DataQuery, DataSourceApi, dateTimeFormat, ExploreUrlState, urlUtil } from '@grafana/data';
import { serializeStateToUrlParam } from '@grafana/data/src/utils/url';
import { getDataSourceSrv } from '@grafana/runtime';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification, createWarningNotification } from 'app/core/copy/appNotification';
import { dispatch } from 'app/store/store';
import { RichHistoryQuery } from 'app/types/explore';
import {
RichHistoryResults,
RichHistoryServiceError,
RichHistoryStorageWarning,
RichHistoryStorageWarningDetails,
} from '../history/RichHistoryStorage';
import { getRichHistoryStorage } from '../history/richHistoryStorageProvider';
import { RichHistorySearchFilters, RichHistorySettings, SortOrder } from './richHistoryTypes';
export { RichHistorySearchFilters, RichHistorySettings, SortOrder };
/*
* Add queries to rich history. Save only queries within the retention period, or that are starred.
* Side-effect: store history in local storage
*/
export async function addToRichHistory(
datasourceUid: string,
datasourceName: string | null,
queries: DataQuery[],
starred: boolean,
comment: string | null,
showQuotaExceededError: boolean,
showLimitExceededWarning: boolean
): Promise<{ richHistoryStorageFull?: boolean; limitExceeded?: boolean }> {
/* Save only queries, that are not falsy (e.g. empty object, null, ...) */
const newQueriesToSave: DataQuery[] = queries && queries.filter((query) => notEmptyQuery(query));
if (newQueriesToSave.length > 0) {
let richHistoryStorageFull = false;
let limitExceeded = false;
let warning: RichHistoryStorageWarningDetails | undefined;
try {
const result = await getRichHistoryStorage().addToRichHistory({
datasourceUid: datasourceUid,
datasourceName: datasourceName ?? '',
queries: newQueriesToSave,
starred,
comment: comment ?? '',
});
warning = result.warning;
} catch (error) {
if (error instanceof Error) {
if (error.name === RichHistoryServiceError.StorageFull) {
richHistoryStorageFull = true;
showQuotaExceededError && dispatch(notifyApp(createErrorNotification(error.message)));
} else if (error.name !== RichHistoryServiceError.DuplicatedEntry) {
dispatch(notifyApp(createErrorNotification('Rich History update failed', error.message)));
}
}
// Saving failed. Do not add new entry.
return { richHistoryStorageFull, limitExceeded };
}
// Limit exceeded but new entry was added. Notify that old entries have been removed.
if (warning && warning.type === RichHistoryStorageWarning.LimitExceeded) {
limitExceeded = true;
showLimitExceededWarning && dispatch(notifyApp(createWarningNotification(warning.message)));
}
return { richHistoryStorageFull, limitExceeded };
}
// Nothing to change
return {};
}
export async function getRichHistory(filters: RichHistorySearchFilters): Promise<RichHistoryResults> {
return await getRichHistoryStorage().getRichHistory(filters);
}
export async function updateRichHistorySettings(settings: RichHistorySettings): Promise<void> {
await getRichHistoryStorage().updateSettings(settings);
}
export async function getRichHistorySettings(): Promise<RichHistorySettings> {
return await getRichHistoryStorage().getSettings();
}
export async function deleteAllFromRichHistory(): Promise<void> {
return getRichHistoryStorage().deleteAll();
}
export async function updateStarredInRichHistory(id: string, starred: boolean) {
try {
return await getRichHistoryStorage().updateStarred(id, starred);
} catch (error) {
if (error instanceof Error) {
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
}
return undefined;
}
}
export async function updateCommentInRichHistory(id: string, newComment: string | undefined) {
try {
return await getRichHistoryStorage().updateComment(id, newComment);
} catch (error) {
if (error instanceof Error) {
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
}
return undefined;
}
}
export async function deleteQueryInRichHistory(id: string) {
try {
await getRichHistoryStorage().deleteRichHistory(id);
return id;
} catch (error) {
if (error instanceof Error) {
dispatch(notifyApp(createErrorNotification('Saving rich history failed', error.message)));
}
return undefined;
}
}
export const createUrlFromRichHistory = (query: RichHistoryQuery) => {
const exploreState: ExploreUrlState = {
/* Default range, as we are not saving timerange in rich history */
range: { from: 'now-1h', to: 'now' },
datasource: query.datasourceName,
queries: query.queries,
};
const serializedState = serializeStateToUrlParam(exploreState);
const baseUrl = /.*(?=\/explore)/.exec(`${window.location.href}`)![0];
const url = urlUtil.renderUrl(`${baseUrl}/explore`, { left: serializedState });
return url;
};
/* Needed for slider in Rich history to map numerical values to meaningful strings */
export const mapNumbertoTimeInSlider = (num: number) => {
let str;
switch (num) {
case 0:
str = 'today';
break;
case 1:
str = 'yesterday';
break;
case 7:
str = 'a week ago';
break;
case 14:
str = 'two weeks ago';
break;
default:
str = `${num} days ago`;
}
return str;
};
export function createDateStringFromTs(ts: number) {
return dateTimeFormat(ts, {
format: 'MMMM D',
});
}
export function getQueryDisplayText(query: DataQuery): string {
/* 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) {
let heading = '';
if (sortOrder === SortOrder.DatasourceAZ || sortOrder === SortOrder.DatasourceZA) {
heading = query.datasourceName;
} else {
heading = createDateStringFromTs(query.createdAt);
}
return heading;
}
export function createQueryText(query: DataQuery, dsApi?: DataSourceApi) {
if (dsApi?.getQueryDisplayText) {
return dsApi.getQueryDisplayText(query);
}
return getQueryDisplayText(query);
}
export function mapQueriesToHeadings(query: RichHistoryQuery[], sortOrder: SortOrder) {
let mappedQueriesToHeadings: Record<string, RichHistoryQuery[]> = {};
query.forEach((q) => {
let heading = createQueryHeading(q, sortOrder);
if (!(heading in mappedQueriesToHeadings)) {
mappedQueriesToHeadings[heading] = [q];
} else {
mappedQueriesToHeadings[heading] = [...mappedQueriesToHeadings[heading], q];
}
});
return mappedQueriesToHeadings;
}
/*
* Create a list of all available data sources
*/
export function createDatasourcesList() {
return getDataSourceSrv()
.getList({ mixed: true })
.map((dsSettings) => {
return {
name: dsSettings.name,
uid: dsSettings.uid,
};
});
}
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;
}