mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Sort order of log results (#26669)
* Create sorting button and functionality * Set up logs ordering * Add tests * Refactor * Refactor * Replace new button with old * Move SortOrder enum to grafana/data * Update SortOrder in test * Update context based on sort order of logs * Update used method for panel, update tests * Rename prop to logsSortOrder * Memoize resuults * Add title too button * Add disablinng of button for 1sec * Update wordiing * Update packages/grafana-data/src/utils/logs.ts Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * Update packages/grafana-data/src/utils/logs.ts Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * Update test by reordering logs * Clear timers, add button flipping title Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
@@ -28,10 +28,11 @@ import {
|
||||
textUtil,
|
||||
dateTime,
|
||||
AbsoluteTimeRange,
|
||||
sortInAscendingOrder,
|
||||
} from '@grafana/data';
|
||||
import { getThemeColor } from 'app/core/utils/colors';
|
||||
|
||||
import { sortInAscendingOrder, deduplicateLogRowsById } from 'app/core/utils/explore';
|
||||
import { deduplicateLogRowsById } from 'app/core/utils/explore';
|
||||
import { decimalSIPrefix } from '@grafana/data/src/valueFormats/symbolFormatters';
|
||||
|
||||
export const LogLevelColor = {
|
||||
|
||||
@@ -8,23 +8,12 @@ import {
|
||||
hasNonEmptyQuery,
|
||||
parseUrlState,
|
||||
refreshIntervalToSortOrder,
|
||||
sortLogsResult,
|
||||
SortOrder,
|
||||
updateHistory,
|
||||
getExploreUrl,
|
||||
GetExploreUrlArguments,
|
||||
} from './explore';
|
||||
import store from 'app/core/store';
|
||||
import {
|
||||
DataQueryError,
|
||||
dateTime,
|
||||
LogLevel,
|
||||
LogRowModel,
|
||||
LogsDedupStrategy,
|
||||
LogsModel,
|
||||
MutableDataFrame,
|
||||
ExploreUrlState,
|
||||
} from '@grafana/data';
|
||||
import { DataQueryError, dateTime, LogsDedupStrategy, ExploreUrlState, LogsSortOrder } from '@grafana/data';
|
||||
import { RefreshPicker } from '@grafana/ui';
|
||||
import { serializeStateToUrlParam } from '@grafana/data/src/utils/url';
|
||||
|
||||
@@ -392,7 +381,7 @@ describe('refreshIntervalToSortOrder', () => {
|
||||
it('then it should return ascending', () => {
|
||||
const result = refreshIntervalToSortOrder(RefreshPicker.liveOption.value);
|
||||
|
||||
expect(result).toBe(SortOrder.Ascending);
|
||||
expect(result).toBe(LogsSortOrder.Ascending);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -400,7 +389,7 @@ describe('refreshIntervalToSortOrder', () => {
|
||||
it('then it should return descending', () => {
|
||||
const result = refreshIntervalToSortOrder(RefreshPicker.offOption.value);
|
||||
|
||||
expect(result).toBe(SortOrder.Descending);
|
||||
expect(result).toBe(LogsSortOrder.Descending);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -408,7 +397,7 @@ describe('refreshIntervalToSortOrder', () => {
|
||||
it('then it should return descending', () => {
|
||||
const result = refreshIntervalToSortOrder('5s');
|
||||
|
||||
expect(result).toBe(SortOrder.Descending);
|
||||
expect(result).toBe(LogsSortOrder.Descending);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -416,102 +405,31 @@ describe('refreshIntervalToSortOrder', () => {
|
||||
it('then it should return descending', () => {
|
||||
const result = refreshIntervalToSortOrder(undefined);
|
||||
|
||||
expect(result).toBe(SortOrder.Descending);
|
||||
expect(result).toBe(LogsSortOrder.Descending);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortLogsResult', () => {
|
||||
const firstRow: LogRowModel = {
|
||||
rowIndex: 0,
|
||||
entryFieldIndex: 0,
|
||||
dataFrame: new MutableDataFrame(),
|
||||
entry: '',
|
||||
hasAnsi: false,
|
||||
labels: {},
|
||||
logLevel: LogLevel.info,
|
||||
raw: '',
|
||||
timeEpochMs: 0,
|
||||
timeEpochNs: '0',
|
||||
timeFromNow: '',
|
||||
timeLocal: '',
|
||||
timeUtc: '',
|
||||
uid: '1',
|
||||
};
|
||||
const sameAsFirstRow = firstRow;
|
||||
const secondRow: LogRowModel = {
|
||||
rowIndex: 1,
|
||||
entryFieldIndex: 0,
|
||||
dataFrame: new MutableDataFrame(),
|
||||
entry: '',
|
||||
hasAnsi: false,
|
||||
labels: {},
|
||||
logLevel: LogLevel.info,
|
||||
raw: '',
|
||||
timeEpochMs: 10,
|
||||
timeEpochNs: '10000000',
|
||||
timeFromNow: '',
|
||||
timeLocal: '',
|
||||
timeUtc: '',
|
||||
uid: '2',
|
||||
};
|
||||
|
||||
describe('when called with SortOrder.Descending', () => {
|
||||
it('then it should sort descending', () => {
|
||||
const logsResult: LogsModel = {
|
||||
rows: [firstRow, sameAsFirstRow, secondRow],
|
||||
hasUniqueLabels: false,
|
||||
};
|
||||
const result = sortLogsResult(logsResult, SortOrder.Descending);
|
||||
|
||||
expect(result).toEqual({
|
||||
rows: [secondRow, firstRow, sameAsFirstRow],
|
||||
hasUniqueLabels: false,
|
||||
});
|
||||
});
|
||||
describe('when buildQueryTransaction', () => {
|
||||
it('it should calculate interval based on time range', () => {
|
||||
const queries = [{ refId: 'A' }];
|
||||
const queryOptions = { maxDataPoints: 1000, minInterval: '15s' };
|
||||
const range = { from: dateTime().subtract(1, 'd'), to: dateTime(), raw: { from: '1h', to: '1h' } };
|
||||
const transaction = buildQueryTransaction(queries, queryOptions, range, false);
|
||||
expect(transaction.request.intervalMs).toEqual(60000);
|
||||
});
|
||||
|
||||
describe('when called with SortOrder.Ascending', () => {
|
||||
it('then it should sort ascending', () => {
|
||||
const logsResult: LogsModel = {
|
||||
rows: [secondRow, firstRow, sameAsFirstRow],
|
||||
hasUniqueLabels: false,
|
||||
};
|
||||
const result = sortLogsResult(logsResult, SortOrder.Ascending);
|
||||
|
||||
expect(result).toEqual({
|
||||
rows: [firstRow, sameAsFirstRow, secondRow],
|
||||
hasUniqueLabels: false,
|
||||
});
|
||||
});
|
||||
it('it should calculate interval taking minInterval into account', () => {
|
||||
const queries = [{ refId: 'A' }];
|
||||
const queryOptions = { maxDataPoints: 1000, minInterval: '15s' };
|
||||
const range = { from: dateTime().subtract(1, 'm'), to: dateTime(), raw: { from: '1h', to: '1h' } };
|
||||
const transaction = buildQueryTransaction(queries, queryOptions, range, false);
|
||||
expect(transaction.request.intervalMs).toEqual(15000);
|
||||
});
|
||||
|
||||
describe('when buildQueryTransaction', () => {
|
||||
it('it should calculate interval based on time range', () => {
|
||||
const queries = [{ refId: 'A' }];
|
||||
const queryOptions = { maxDataPoints: 1000, minInterval: '15s' };
|
||||
const range = { from: dateTime().subtract(1, 'd'), to: dateTime(), raw: { from: '1h', to: '1h' } };
|
||||
const transaction = buildQueryTransaction(queries, queryOptions, range, false);
|
||||
|
||||
expect(transaction.request.intervalMs).toEqual(60000);
|
||||
});
|
||||
|
||||
it('it should calculate interval taking minInterval into account', () => {
|
||||
const queries = [{ refId: 'A' }];
|
||||
const queryOptions = { maxDataPoints: 1000, minInterval: '15s' };
|
||||
const range = { from: dateTime().subtract(1, 'm'), to: dateTime(), raw: { from: '1h', to: '1h' } };
|
||||
const transaction = buildQueryTransaction(queries, queryOptions, range, false);
|
||||
|
||||
expect(transaction.request.intervalMs).toEqual(15000);
|
||||
});
|
||||
|
||||
it('it should calculate interval taking maxDataPoints into account', () => {
|
||||
const queries = [{ refId: 'A' }];
|
||||
const queryOptions = { maxDataPoints: 10, minInterval: '15s' };
|
||||
const range = { from: dateTime().subtract(1, 'd'), to: dateTime(), raw: { from: '1h', to: '1h' } };
|
||||
const transaction = buildQueryTransaction(queries, queryOptions, range, false);
|
||||
|
||||
expect(transaction.request.interval).toEqual('2h');
|
||||
});
|
||||
it('it should calculate interval taking maxDataPoints into account', () => {
|
||||
const queries = [{ refId: 'A' }];
|
||||
const queryOptions = { maxDataPoints: 10, minInterval: '15s' };
|
||||
const range = { from: dateTime().subtract(1, 'd'), to: dateTime(), raw: { from: '1h', to: '1h' } };
|
||||
const transaction = buildQueryTransaction(queries, queryOptions, range, false);
|
||||
expect(transaction.request.interval).toEqual('2h');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
IntervalValues,
|
||||
LogRowModel,
|
||||
LogsDedupStrategy,
|
||||
LogsModel,
|
||||
LogsSortOrder,
|
||||
RawTimeRange,
|
||||
TimeFragment,
|
||||
TimeRange,
|
||||
@@ -464,67 +464,8 @@ export const getRefIds = (value: any): string[] => {
|
||||
return _.uniq(_.flatten(refIds));
|
||||
};
|
||||
|
||||
export const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
||||
// compare milliseconds
|
||||
if (a.timeEpochMs < b.timeEpochMs) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.timeEpochMs > b.timeEpochMs) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if milliseonds are equal, compare nanoseconds
|
||||
if (a.timeEpochNs < b.timeEpochNs) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.timeEpochNs > b.timeEpochNs) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
||||
// compare milliseconds
|
||||
if (a.timeEpochMs > b.timeEpochMs) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.timeEpochMs < b.timeEpochMs) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if milliseonds are equal, compare nanoseconds
|
||||
if (a.timeEpochNs > b.timeEpochNs) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.timeEpochNs < b.timeEpochNs) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
export enum SortOrder {
|
||||
Descending = 'Descending',
|
||||
Ascending = 'Ascending',
|
||||
DatasourceAZ = 'Datasource A-Z',
|
||||
DatasourceZA = 'Datasource Z-A',
|
||||
}
|
||||
|
||||
export const refreshIntervalToSortOrder = (refreshInterval?: string) =>
|
||||
RefreshPicker.isLive(refreshInterval) ? SortOrder.Ascending : SortOrder.Descending;
|
||||
|
||||
export const sortLogsResult = (logsResult: LogsModel | null, sortOrder: SortOrder): LogsModel => {
|
||||
const rows = logsResult ? logsResult.rows : [];
|
||||
sortOrder === SortOrder.Ascending ? rows.sort(sortInAscendingOrder) : rows.sort(sortInDescendingOrder);
|
||||
const result: LogsModel = logsResult ? { ...logsResult, rows } : { hasUniqueLabels: false, rows };
|
||||
|
||||
return result;
|
||||
};
|
||||
RefreshPicker.isLive(refreshInterval) ? LogsSortOrder.Ascending : LogsSortOrder.Descending;
|
||||
|
||||
export const convertToWebSocketUrl = (url: string) => {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
filterAndSortQueries,
|
||||
} from './richHistory';
|
||||
import store from 'app/core/store';
|
||||
import { SortOrder } from './explore';
|
||||
import { SortOrder } from './richHistory';
|
||||
import { dateTime, DataQuery } from '@grafana/data';
|
||||
|
||||
const mock: any = {
|
||||
|
||||
@@ -5,7 +5,6 @@ import _ from 'lodash';
|
||||
import { DataQuery, DataSourceApi, dateTimeFormat, AppEvents, urlUtil, ExploreUrlState } from '@grafana/data';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import store from 'app/core/store';
|
||||
import { SortOrder } from './explore';
|
||||
import { getExploreDatasources } from '../../features/explore/state/selectors';
|
||||
|
||||
// Types
|
||||
@@ -21,6 +20,13 @@ export const RICH_HISTORY_SETTING_KEYS = {
|
||||
datasourceFilters: 'grafana.explore.richHistory.datasourceFilters',
|
||||
};
|
||||
|
||||
export enum SortOrder {
|
||||
Descending = 'Descending',
|
||||
Ascending = 'Ascending',
|
||||
DatasourceAZ = 'Datasource A-Z',
|
||||
DatasourceZA = 'Datasource Z-A',
|
||||
}
|
||||
|
||||
/*
|
||||
* Add queries to rich history. Save only queries within the retention period, or that are starred.
|
||||
* Side-effect: store history in local storage
|
||||
|
||||
Reference in New Issue
Block a user