mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: Sync query direction with sort order in Explore and Dashboards (#98722)
* Logs: sync direction and sort order for loki queries in explore * Logs: emit event on sort order change * Loki query editor: listen to sort change events and update direction * Loki query editor: unsubscribe to sort event * Logs: don't publish events in Explore * LokiQueryBuilderOptions: use stored order as default value when in explore * Query builder options: initialize query direction * Logs: unit test * LogsPanel: update unit test * Update tests * LokiQueryBuilderOptions: unit test * Update public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx Co-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> * Update public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx --------- Co-authored-by: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com>
This commit is contained in:
parent
90e65f6ce6
commit
3eace5f7c8
@ -764,6 +764,8 @@ export {
|
||||
type DataSourceWithQueryModificationSupport,
|
||||
hasToggleableQueryFiltersSupport,
|
||||
hasQueryModificationSupport,
|
||||
LogSortOrderChangeEvent,
|
||||
type LogSortOrderChangePayload,
|
||||
} from './types/logs';
|
||||
export {
|
||||
type AnnotationQuery,
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { DataQuery, LogsSortOrder } from '@grafana/schema';
|
||||
|
||||
import { BusEventWithPayload } from '../events/types';
|
||||
|
||||
import { KeyValue, Labels } from './data';
|
||||
import { DataFrame } from './dataFrame';
|
||||
@ -366,3 +368,11 @@ export const hasQueryModificationSupport = <TQuery extends DataQuery>(
|
||||
'getSupportedQueryModifications' in datasource
|
||||
);
|
||||
};
|
||||
|
||||
export interface LogSortOrderChangePayload {
|
||||
order: LogsSortOrder;
|
||||
}
|
||||
|
||||
export class LogSortOrderChangeEvent extends BusEventWithPayload<LogSortOrderChangePayload> {
|
||||
static type = 'logs-sort-order-change';
|
||||
}
|
||||
|
@ -14,10 +14,12 @@ import {
|
||||
toUtc,
|
||||
createDataFrame,
|
||||
ExploreLogsPanelState,
|
||||
DataQuery,
|
||||
} from '@grafana/data';
|
||||
import { organizeFieldsTransformer } from '@grafana/data/src/transformations/transformers/organize';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { extractFieldsTransformer } from 'app/features/transformers/extractFields/extractFields';
|
||||
import { LokiQueryDirection } from 'app/plugins/datasource/loki/dataquery.gen';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
|
||||
import { initialExploreState } from '../state/main';
|
||||
@ -46,6 +48,17 @@ jest.mock('../state/explorePane', () => ({
|
||||
changePanelState: (exploreId: string, panel: 'logs', panelState: {} | ExploreLogsPanelState) => {
|
||||
return fakeChangePanelState(exploreId, panel, panelState);
|
||||
},
|
||||
changeQueries: (args: { queries: DataQuery[]; exploreId: string | undefined }) => {
|
||||
return fakeChangeQueries(args);
|
||||
},
|
||||
}));
|
||||
|
||||
const fakeChangeQueries = jest.fn().mockReturnValue({ type: 'fakeChangeQueries' });
|
||||
jest.mock('../state/query', () => ({
|
||||
...jest.requireActual('../state/query'),
|
||||
changeQueries: (args: { queries: DataQuery[]; exploreId: string | undefined }) => {
|
||||
return fakeChangeQueries(args);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('Logs', () => {
|
||||
@ -377,6 +390,26 @@ describe('Logs', () => {
|
||||
expect(logRows[2].textContent).toContain('log message 3');
|
||||
});
|
||||
|
||||
it('should sync the query direction when changing the order of loki queries', async () => {
|
||||
const query = { expr: '{a="b"}', refId: 'A', datasource: { type: 'loki' } };
|
||||
setup({ logsQueries: [query] });
|
||||
const oldestFirstSelection = screen.getByLabelText('Oldest first');
|
||||
await userEvent.click(oldestFirstSelection);
|
||||
expect(fakeChangeQueries).toHaveBeenCalledWith({
|
||||
exploreId: 'left',
|
||||
queries: [{ ...query, direction: LokiQueryDirection.Forward }],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change the query direction when changing the order of non-loki queries', async () => {
|
||||
fakeChangeQueries.mockClear();
|
||||
const query = { refId: 'B' };
|
||||
setup({ logsQueries: [query] });
|
||||
const oldestFirstSelection = screen.getByLabelText('Oldest first');
|
||||
await userEvent.click(oldestFirstSelection);
|
||||
expect(fakeChangeQueries).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('for permalinking', () => {
|
||||
it('should dispatch a `changePanelState` event without the id', () => {
|
||||
const panelState = { logs: { id: '1' } };
|
||||
|
@ -58,6 +58,7 @@ import { LogRowContextModal } from 'app/features/logs/components/log-context/Log
|
||||
import { LogLevelColor, dedupLogRows, filterLogLevels } from 'app/features/logs/logsModel';
|
||||
import { getLogLevel, getLogLevelFromKey, getLogLevelInfo } from 'app/features/logs/utils';
|
||||
import { LokiQueryDirection } from 'app/plugins/datasource/loki/dataquery.gen';
|
||||
import { isLokiQuery } from 'app/plugins/datasource/loki/queryUtils';
|
||||
import { getState } from 'app/store/store';
|
||||
import { ExploreItemState, useDispatch } from 'app/types';
|
||||
|
||||
@ -72,6 +73,7 @@ import {
|
||||
import { useContentOutlineContext } from '../ContentOutline/ContentOutlineContext';
|
||||
import { getUrlStateFromPaneState } from '../hooks/useStateSync';
|
||||
import { changePanelState } from '../state/explorePane';
|
||||
import { changeQueries } from '../state/query';
|
||||
|
||||
import { LogsFeedback } from './LogsFeedback';
|
||||
import { LogsMetaRow } from './LogsMetaRow';
|
||||
@ -468,6 +470,31 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
|
||||
const newSortOrder =
|
||||
logsSortOrder === LogsSortOrder.Descending ? LogsSortOrder.Ascending : LogsSortOrder.Descending;
|
||||
store.set(SETTINGS_KEYS.logsSortOrder, newSortOrder);
|
||||
if (logsQueries) {
|
||||
let hasLokiQueries = false;
|
||||
const newQueries = logsQueries.map((query) => {
|
||||
if (query.datasource?.type !== 'loki' || !isLokiQuery(query)) {
|
||||
return query;
|
||||
}
|
||||
hasLokiQueries = true;
|
||||
|
||||
if (query.direction === LokiQueryDirection.Scan) {
|
||||
// Don't override Scan. When the direction is Scan it means that the user specifically assigned this direction to the query.
|
||||
return query;
|
||||
}
|
||||
const newDirection =
|
||||
newSortOrder === LogsSortOrder.Ascending ? LokiQueryDirection.Forward : LokiQueryDirection.Backward;
|
||||
if (newDirection !== query.direction) {
|
||||
query.direction = newDirection;
|
||||
}
|
||||
return query;
|
||||
});
|
||||
|
||||
if (hasLokiQueries) {
|
||||
dispatch(changeQueries({ exploreId, queries: newQueries }));
|
||||
}
|
||||
}
|
||||
|
||||
setLogsSortOrder(newSortOrder);
|
||||
}, 0);
|
||||
cancelFlippingTimer.current = window.setTimeout(() => setIsFlipping(false), 1000);
|
||||
|
@ -15,6 +15,9 @@ import { LokiQueryEditorProps } from './types';
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getAppEvents: jest.fn().mockReturnValue({
|
||||
subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
|
||||
}),
|
||||
reportInteraction: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
@ -9,6 +9,13 @@ import { testIds as regularTestIds } from './LokiQueryEditor';
|
||||
import { LokiQueryEditorByApp } from './LokiQueryEditorByApp';
|
||||
import { testIds as alertingTestIds } from './LokiQueryEditorForAlerting';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getAppEvents: jest.fn().mockReturnValue({
|
||||
subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
|
||||
}),
|
||||
}));
|
||||
|
||||
function setup(app: CoreApp): RenderResult {
|
||||
const dataSource = createLokiDatasource();
|
||||
dataSource.metadataRequest = jest.fn();
|
||||
|
@ -6,6 +6,13 @@ import { createLokiDatasource } from '../../__mocks__/datasource';
|
||||
|
||||
import { MonacoQueryFieldWrapper, Props } from './MonacoQueryFieldWrapper';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getAppEvents: jest.fn().mockReturnValue({
|
||||
subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }),
|
||||
}),
|
||||
}));
|
||||
|
||||
function renderComponent({ initialValue = '', onChange = jest.fn(), onRunQuery = jest.fn() }: Partial<Props> = {}) {
|
||||
const datasource = createLokiDatasource();
|
||||
|
||||
|
@ -1,9 +1,37 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { LokiQuery, LokiQueryType } from '../../types';
|
||||
import { CoreApp, LogSortOrderChangeEvent, LogsSortOrder, store } from '@grafana/data';
|
||||
import { config, getAppEvents } from '@grafana/runtime';
|
||||
|
||||
import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions';
|
||||
import { LokiQuery, LokiQueryDirection, LokiQueryType } from '../../types';
|
||||
|
||||
import { LokiQueryBuilderOptions, Props } from './LokiQueryBuilderOptions';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
config: {
|
||||
...jest.requireActual('@grafana/runtime').config,
|
||||
featureToggles: {
|
||||
...jest.requireActual('@grafana/runtime').featureToggles,
|
||||
lokiShardSplitting: true,
|
||||
},
|
||||
},
|
||||
getAppEvents: jest.fn(),
|
||||
}));
|
||||
|
||||
const subscribeMock = jest.fn();
|
||||
beforeAll(() => {
|
||||
config.featureToggles.lokiShardSplitting = true;
|
||||
subscribeMock.mockImplementation(() => ({ unsubscribe: jest.fn() }));
|
||||
jest.mocked(getAppEvents).mockReturnValue({
|
||||
publish: jest.fn(),
|
||||
getStream: jest.fn(),
|
||||
subscribe: subscribeMock,
|
||||
removeAllListeners: jest.fn(),
|
||||
newScopedBus: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
describe('LokiQueryBuilderOptions', () => {
|
||||
it('can change query type', async () => {
|
||||
@ -86,7 +114,7 @@ describe('LokiQueryBuilderOptions', () => {
|
||||
});
|
||||
|
||||
it('shows correct options for log query', async () => {
|
||||
setup({ expr: '{foo="bar"}' });
|
||||
setup({ expr: '{foo="bar"}', direction: LokiQueryDirection.Backward });
|
||||
expect(screen.getByText('Line limit: 20')).toBeInTheDocument();
|
||||
expect(screen.getByText('Type: Range')).toBeInTheDocument();
|
||||
expect(screen.getByText('Direction: Backward')).toBeInTheDocument();
|
||||
@ -184,9 +212,91 @@ describe('LokiQueryBuilderOptions', () => {
|
||||
step: '4s',
|
||||
});
|
||||
});
|
||||
|
||||
describe('Query direction', () => {
|
||||
it("initializes query direction when it's empty", async () => {
|
||||
const onChange = jest.fn();
|
||||
setup({ expr: '{foo="bar"}' }, onChange);
|
||||
await waitFor(() =>
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
expr: '{foo="bar"}',
|
||||
refId: 'A',
|
||||
direction: LokiQueryDirection.Backward,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('uses backward as default in Explore with no previous stored preference', async () => {
|
||||
const onChange = jest.fn();
|
||||
store.delete('grafana.explore.logs.sortOrder');
|
||||
setup({ expr: '{foo="bar"}' }, onChange, { app: CoreApp.Explore });
|
||||
await waitFor(() =>
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
expr: '{foo="bar"}',
|
||||
refId: 'A',
|
||||
direction: LokiQueryDirection.Backward,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the stored sorting option to determine direction in Explore', async () => {
|
||||
store.set('grafana.explore.logs.sortOrder', LogsSortOrder.Ascending);
|
||||
const onChange = jest.fn();
|
||||
setup({ expr: '{foo="bar"}' }, onChange, { app: CoreApp.Explore });
|
||||
await waitFor(() =>
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
expr: '{foo="bar"}',
|
||||
refId: 'A',
|
||||
direction: LokiQueryDirection.Forward,
|
||||
})
|
||||
);
|
||||
store.delete('grafana.explore.logs.sortOrder');
|
||||
});
|
||||
|
||||
describe('Event handling', () => {
|
||||
let listener: (event: LogSortOrderChangeEvent) => void = jest.fn();
|
||||
const onChangeMock = jest.fn();
|
||||
beforeEach(() => {
|
||||
onChangeMock.mockClear();
|
||||
listener = jest.fn();
|
||||
subscribeMock.mockImplementation((_: unknown, callback: (event: LogSortOrderChangeEvent) => void) => {
|
||||
listener = callback;
|
||||
return { unsubscribe: jest.fn() };
|
||||
});
|
||||
});
|
||||
it('subscribes to sort change event and updates the direction', () => {
|
||||
setup({ expr: '{foo="bar"}', direction: LokiQueryDirection.Backward }, onChangeMock, {
|
||||
app: CoreApp.Dashboard,
|
||||
});
|
||||
expect(screen.getByText(/Direction: Backward/)).toBeInTheDocument();
|
||||
listener(
|
||||
new LogSortOrderChangeEvent({
|
||||
order: LogsSortOrder.Ascending,
|
||||
})
|
||||
);
|
||||
expect(onChangeMock).toHaveBeenCalledTimes(1);
|
||||
expect(onChangeMock).toHaveBeenCalledWith({
|
||||
direction: 'forward',
|
||||
expr: '{foo="bar"}',
|
||||
refId: 'A',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not change the direction when the current direction is scan', () => {
|
||||
setup({ expr: '{foo="bar"}', direction: LokiQueryDirection.Scan }, onChangeMock, { app: CoreApp.Dashboard });
|
||||
expect(screen.getByText(/Direction: Scan/)).toBeInTheDocument();
|
||||
listener(
|
||||
new LogSortOrderChangeEvent({
|
||||
order: LogsSortOrder.Ascending,
|
||||
})
|
||||
);
|
||||
expect(onChangeMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setup(queryOverrides: Partial<LokiQuery> = {}, onChange = jest.fn()) {
|
||||
function setup(queryOverrides: Partial<LokiQuery> = {}, onChange = jest.fn(), propOverrides: Partial<Props> = {}) {
|
||||
const props = {
|
||||
query: {
|
||||
refId: 'A',
|
||||
@ -197,6 +307,7 @@ function setup(queryOverrides: Partial<LokiQuery> = {}, onChange = jest.fn()) {
|
||||
onChange,
|
||||
maxLines: 20,
|
||||
queryStats: { streams: 0, chunks: 0, bytes: 0, entries: 0 },
|
||||
...propOverrides,
|
||||
};
|
||||
|
||||
const { container } = render(<LokiQueryBuilderOptions {...props} />);
|
||||
|
@ -1,10 +1,18 @@
|
||||
import { trim } from 'lodash';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { CoreApp, isValidDuration, isValidGrafanaDuration, SelectableValue } from '@grafana/data';
|
||||
import {
|
||||
CoreApp,
|
||||
isValidDuration,
|
||||
isValidGrafanaDuration,
|
||||
LogSortOrderChangeEvent,
|
||||
LogsSortOrder,
|
||||
SelectableValue,
|
||||
store,
|
||||
} from '@grafana/data';
|
||||
import { EditorField, EditorRow, QueryOptionGroup } from '@grafana/experimental';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { config, getAppEvents, reportInteraction } from '@grafana/runtime';
|
||||
import { Alert, AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
@ -30,6 +38,13 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
|
||||
({ app, query, onChange, onRunQuery, maxLines, queryStats }) => {
|
||||
const [splitDurationValid, setSplitDurationValid] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize the query direction according to the current environment.
|
||||
if (!query.direction) {
|
||||
onChange({ ...query, direction: getDefaultQueryDirection(app) });
|
||||
}
|
||||
}, [app, onChange, query]);
|
||||
|
||||
useEffect(() => {
|
||||
if (query.step && !isValidGrafanaDuration(`${query.step}`) && parseInt(query.step, 10)) {
|
||||
onChange({
|
||||
@ -44,10 +59,13 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
const onQueryDirectionChange = (value: LokiQueryDirection) => {
|
||||
onChange({ ...query, direction: value });
|
||||
onRunQuery();
|
||||
};
|
||||
const onQueryDirectionChange = useCallback(
|
||||
(value: LokiQueryDirection) => {
|
||||
onChange({ ...query, direction: value });
|
||||
onRunQuery();
|
||||
},
|
||||
[onChange, onRunQuery, query]
|
||||
);
|
||||
|
||||
const onResolutionChange = (option: SelectableValue<number>) => {
|
||||
reportInteraction('grafana_loki_resolution_clicked', {
|
||||
@ -87,14 +105,33 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
|
||||
onRunQuery();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (app !== CoreApp.Dashboard && app !== CoreApp.PanelEditor) {
|
||||
return;
|
||||
}
|
||||
const subscription = getAppEvents().subscribe(LogSortOrderChangeEvent, (sortEvent: LogSortOrderChangeEvent) => {
|
||||
if (query.direction === LokiQueryDirection.Scan) {
|
||||
return;
|
||||
}
|
||||
const newDirection =
|
||||
sortEvent.payload.order === LogsSortOrder.Ascending
|
||||
? LokiQueryDirection.Forward
|
||||
: LokiQueryDirection.Backward;
|
||||
if (newDirection !== query.direction) {
|
||||
onQueryDirectionChange(newDirection);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [app, onQueryDirectionChange, query.direction]);
|
||||
|
||||
let queryType = getLokiQueryType(query);
|
||||
const isLogQuery = isLogsQuery(query.expr);
|
||||
const filteredQueryTypeOptions = isLogQuery
|
||||
? queryTypeOptions.filter((o) => o.value !== LokiQueryType.Instant)
|
||||
: queryTypeOptions;
|
||||
|
||||
const queryDirection = query.direction ?? LokiQueryDirection.Backward;
|
||||
|
||||
// if the state's queryType is still Instant, trigger a change to range for log queries
|
||||
if (isLogQuery && queryType === LokiQueryType.Instant) {
|
||||
onChange({ ...query, queryType: LokiQueryType.Range });
|
||||
@ -112,7 +149,7 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
|
||||
<EditorRow>
|
||||
<QueryOptionGroup
|
||||
title="Options"
|
||||
collapsedInfo={getCollapsedInfo(query, queryType, maxLines, isLogQuery, isValidStep, queryDirection)}
|
||||
collapsedInfo={getCollapsedInfo(query, queryType, maxLines, isLogQuery, isValidStep, query.direction)}
|
||||
queryStats={queryStats}
|
||||
>
|
||||
<EditorField
|
||||
@ -145,7 +182,7 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
|
||||
/>
|
||||
</EditorField>
|
||||
<EditorField label="Direction" tooltip="Direction to search for logs.">
|
||||
<RadioButtonGroup options={queryDirections} value={queryDirection} onChange={onQueryDirectionChange} />
|
||||
<RadioButtonGroup options={queryDirections} value={query.direction} onChange={onQueryDirectionChange} />
|
||||
</EditorField>
|
||||
</>
|
||||
)}
|
||||
@ -214,7 +251,7 @@ function getCollapsedInfo(
|
||||
maxLines: number,
|
||||
isLogQuery: boolean,
|
||||
isValidStep: boolean,
|
||||
direction: LokiQueryDirection
|
||||
direction: LokiQueryDirection | undefined
|
||||
): string[] {
|
||||
const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryType);
|
||||
const resolutionLabel = RESOLUTION_OPTIONS.find((x) => x.value === (query.resolution ?? 1));
|
||||
@ -227,7 +264,7 @@ function getCollapsedInfo(
|
||||
|
||||
items.push(`Type: ${queryTypeLabel?.label}`);
|
||||
|
||||
if (isLogQuery) {
|
||||
if (isLogQuery && direction) {
|
||||
items.push(`Line limit: ${query.maxLines ?? maxLines}`);
|
||||
items.push(`Direction: ${getQueryDirectionLabel(direction)}`);
|
||||
} else {
|
||||
@ -243,4 +280,20 @@ function getCollapsedInfo(
|
||||
return items;
|
||||
}
|
||||
|
||||
function getDefaultQueryDirection(app?: CoreApp) {
|
||||
if (app !== CoreApp.Explore) {
|
||||
/**
|
||||
* The default direction is backward because the default sort order is Descending.
|
||||
* See:
|
||||
* - public/app/features/explore/Logs/Logs.tsx
|
||||
* - public/app/plugins/panel/logs/module.tsx
|
||||
*/
|
||||
return LokiQueryDirection.Backward;
|
||||
}
|
||||
// See app/features/explore/Logs/utils/logs
|
||||
const key = 'grafana.explore.logs.sortOrder';
|
||||
const storedOrder = store.get(key) || LogsSortOrder.Descending;
|
||||
return storedOrder === LogsSortOrder.Ascending ? LokiQueryDirection.Forward : LokiQueryDirection.Backward;
|
||||
}
|
||||
|
||||
LokiQueryBuilderOptions.displayName = 'LokiQueryBuilderOptions';
|
||||
|
@ -13,7 +13,9 @@ import {
|
||||
LogsDedupStrategy,
|
||||
EventBusSrv,
|
||||
DataFrameType,
|
||||
LogSortOrderChangeEvent,
|
||||
} from '@grafana/data';
|
||||
import { getAppEvents } from '@grafana/runtime';
|
||||
import * as grafanaUI from '@grafana/ui';
|
||||
import * as styles from 'app/features/logs/components/getLogRowStyles';
|
||||
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
|
||||
@ -39,6 +41,7 @@ const datasourceSrv = new DatasourceSrvMock(defaultDs, {
|
||||
const getDataSourceSrvMock = jest.fn().mockReturnValue(datasourceSrv);
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getAppEvents: jest.fn(),
|
||||
getDataSourceSrv: () => getDataSourceSrvMock(),
|
||||
}));
|
||||
|
||||
@ -69,7 +72,35 @@ const defaultProps = {
|
||||
scopedVars: {},
|
||||
startTime: 1,
|
||||
},
|
||||
series: [],
|
||||
series: [
|
||||
createDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{
|
||||
name: 'timestamp',
|
||||
type: FieldType.time,
|
||||
values: ['2019-04-26T09:28:11.352440161Z'],
|
||||
},
|
||||
{
|
||||
name: 'body',
|
||||
type: FieldType.string,
|
||||
values: ['logline text'],
|
||||
},
|
||||
{
|
||||
name: 'labels',
|
||||
type: FieldType.other,
|
||||
values: [
|
||||
{
|
||||
app: 'common_app',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
type: DataFrameType.LogLines,
|
||||
},
|
||||
}),
|
||||
],
|
||||
state: LoadingState.Done,
|
||||
timeRange: getDefaultTimeRange(),
|
||||
},
|
||||
@ -103,7 +134,32 @@ const defaultProps = {
|
||||
onChangeTimeRange: jest.fn(),
|
||||
};
|
||||
|
||||
const publishMock = jest.fn();
|
||||
beforeAll(() => {
|
||||
jest.mocked(getAppEvents).mockReturnValue({
|
||||
publish: publishMock,
|
||||
getStream: jest.fn(),
|
||||
subscribe: jest.fn(),
|
||||
removeAllListeners: jest.fn(),
|
||||
newScopedBus: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
describe('LogsPanel', () => {
|
||||
it('publishes an event with the current sort order', async () => {
|
||||
publishMock.mockClear();
|
||||
setup();
|
||||
|
||||
await screen.findByText('logline text');
|
||||
|
||||
expect(publishMock).toHaveBeenCalledTimes(1);
|
||||
expect(publishMock).toHaveBeenCalledWith(
|
||||
new LogSortOrderChangeEvent({
|
||||
order: LogsSortOrder.Descending,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('when returned series include common labels', () => {
|
||||
const seriesWithCommonLabels = [
|
||||
createDataFrame({
|
||||
|
@ -27,9 +27,10 @@ import {
|
||||
TimeZone,
|
||||
toUtc,
|
||||
urlUtil,
|
||||
LogSortOrderChangeEvent,
|
||||
} from '@grafana/data';
|
||||
import { convertRawToRange } from '@grafana/data/src/datetime/rangeutil';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { config, getAppEvents } from '@grafana/runtime';
|
||||
import { ScrollContainer, usePanelContext, useStyles2 } from '@grafana/ui';
|
||||
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
||||
import { InfiniteScroll } from 'app/features/logs/components/InfiniteScroll';
|
||||
@ -143,8 +144,16 @@ export const LogsPanel = ({
|
||||
// Prevents the scroll position to change when new data from infinite scrolling is received
|
||||
const keepScrollPositionRef = useRef(false);
|
||||
let closeCallback = useRef<() => void>();
|
||||
|
||||
const { eventBus, onAddAdHocFilter } = usePanelContext();
|
||||
|
||||
useEffect(() => {
|
||||
getAppEvents().publish(
|
||||
new LogSortOrderChangeEvent({
|
||||
order: sortOrder,
|
||||
})
|
||||
);
|
||||
}, [sortOrder]);
|
||||
|
||||
const onLogRowHover = useCallback(
|
||||
(row?: LogRowModel) => {
|
||||
if (row) {
|
||||
|
Loading…
Reference in New Issue
Block a user