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:
Ivana Huckova
2020-08-06 18:35:49 +02:00
committed by GitHub
parent 4334e34366
commit 4a523c3248
24 changed files with 416 additions and 277 deletions

View File

@@ -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 = {

View File

@@ -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');
});
});

View File

@@ -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://';

View File

@@ -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 = {

View File

@@ -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