* Pass timezone to graph in Explore
* Fix timezone issues for table
* Fix type error
* Update public/app/features/explore/TableContainer.test.tsx
Co-authored-by: Giordano Ricci <me@giordanoricci.com>
* Replace UTC with InternalTimeZones
* Update CEST to cest
Co-authored-by: Giordano Ricci <me@giordanoricci.com>
(cherry picked from commit d28e7b6185)
Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
This commit is contained in:
co-authored by
Ivana Huckova
parent
0df043d592
commit
caea2a4f2d
@@ -82,6 +82,7 @@ export const InspectContent: React.FC<Props> = ({
|
|||||||
isLoading={isDataLoading}
|
isLoading={isDataLoading}
|
||||||
options={dataOptions}
|
options={dataOptions}
|
||||||
onOptionsChange={onDataOptionsChange}
|
onOptionsChange={onDataOptionsChange}
|
||||||
|
timeZone={dashboard.timezone}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<CustomScrollbar autoHeightMin="100%">
|
<CustomScrollbar autoHeightMin="100%">
|
||||||
|
|||||||
@@ -232,13 +232,14 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTablePanel(width: number) {
|
renderTablePanel(width: number) {
|
||||||
const { exploreId, datasourceInstance } = this.props;
|
const { exploreId, datasourceInstance, timeZone } = this.props;
|
||||||
return (
|
return (
|
||||||
<TableContainer
|
<TableContainer
|
||||||
ariaLabel={selectors.pages.Explore.General.table}
|
ariaLabel={selectors.pages.Explore.General.table}
|
||||||
width={width}
|
width={width}
|
||||||
exploreId={exploreId}
|
exploreId={exploreId}
|
||||||
onCellFilterAdded={datasourceInstance?.modifyQuery ? this.onCellFilterAdded : undefined}
|
onCellFilterAdded={datasourceInstance?.modifyQuery ? this.onCellFilterAdded : undefined}
|
||||||
|
timeZone={timeZone}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -303,6 +304,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
|||||||
showLogs,
|
showLogs,
|
||||||
showTrace,
|
showTrace,
|
||||||
showNodeGraph,
|
showNodeGraph,
|
||||||
|
timeZone,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { openDrawer } = this.state;
|
const { openDrawer } = this.state;
|
||||||
const styles = getStyles(theme);
|
const styles = getStyles(theme);
|
||||||
@@ -364,6 +366,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
|||||||
exploreId={exploreId}
|
exploreId={exploreId}
|
||||||
width={width}
|
width={width}
|
||||||
onClose={this.toggleShowQueryInspector}
|
onClose={this.toggleShowQueryInspector}
|
||||||
|
timeZone={timeZone}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ErrorBoundaryAlert>
|
</ErrorBoundaryAlert>
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export function ExploreGraph({
|
|||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
onChangeTimeRange={onChangeTime}
|
onChangeTimeRange={onChangeTime}
|
||||||
|
timeZone={timeZone}
|
||||||
options={
|
options={
|
||||||
{
|
{
|
||||||
tooltip: { mode: tooltipDisplayMode },
|
tooltip: { mode: tooltipDisplayMode },
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { ComponentProps } from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
import { TimeRange, LoadingState } from '@grafana/data';
|
import { TimeRange, LoadingState, InternalTimeZones } from '@grafana/data';
|
||||||
import { ExploreId } from 'app/types';
|
import { ExploreId } from 'app/types';
|
||||||
import { ExploreQueryInspector } from './ExploreQueryInspector';
|
import { ExploreQueryInspector } from './ExploreQueryInspector';
|
||||||
|
|
||||||
@@ -33,6 +33,7 @@ const setup = (propOverrides = {}) => {
|
|||||||
width: 100,
|
width: 100,
|
||||||
exploreId: ExploreId.left,
|
exploreId: ExploreId.left,
|
||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
|
timeZone: InternalTimeZones.utc,
|
||||||
queryResponse: {
|
queryResponse: {
|
||||||
state: LoadingState.Done,
|
state: LoadingState.Done,
|
||||||
series: [],
|
series: [],
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab';
|
|||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
width: number;
|
width: number;
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
|
timeZone: TimeZone;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = DispatchProps & ConnectedProps<typeof connector>;
|
type Props = DispatchProps & ConnectedProps<typeof connector>;
|
||||||
|
|
||||||
export function ExploreQueryInspector(props: Props) {
|
export function ExploreQueryInspector(props: Props) {
|
||||||
const { loading, width, onClose, queryResponse } = props;
|
const { loading, width, onClose, queryResponse, timeZone } = props;
|
||||||
const dataFrames = queryResponse?.series || [];
|
const dataFrames = queryResponse?.series || [];
|
||||||
const error = queryResponse?.error;
|
const error = queryResponse?.error;
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ export function ExploreQueryInspector(props: Props) {
|
|||||||
data={dataFrames}
|
data={dataFrames}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
options={{ withTransforms: false, withFieldConfig: false }}
|
options={{ withTransforms: false, withFieldConfig: false }}
|
||||||
|
timeZone={timeZone}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,41 +1,102 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render, shallow } from 'enzyme';
|
import { screen, render, within } from '@testing-library/react';
|
||||||
import { TableContainer } from './TableContainer';
|
import { TableContainer } from './TableContainer';
|
||||||
import { DataFrame } from '@grafana/data';
|
import { DataFrame, toDataFrame, FieldType, InternalTimeZones } from '@grafana/data';
|
||||||
import { ExploreId } from 'app/types/explore';
|
import { ExploreId } from 'app/types/explore';
|
||||||
|
|
||||||
|
function getTable(): HTMLElement {
|
||||||
|
return screen.getAllByRole('table')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRowsData(rows: HTMLElement[]): Object[] {
|
||||||
|
let content = [];
|
||||||
|
for (let i = 1; i < rows.length; i++) {
|
||||||
|
content.push({
|
||||||
|
time: within(rows[i]).getByText(/2021*/).textContent,
|
||||||
|
text: within(rows[i]).getByText(/test_string_*/).textContent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataFrame = toDataFrame({
|
||||||
|
name: 'A',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'time',
|
||||||
|
type: FieldType.time,
|
||||||
|
values: [1609459200000, 1609470000000, 1609462800000, 1609466400000],
|
||||||
|
config: {
|
||||||
|
custom: {
|
||||||
|
filterable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'text',
|
||||||
|
type: FieldType.string,
|
||||||
|
values: ['test_string_1', 'test_string_2', 'test_string_3', 'test_string_4'],
|
||||||
|
config: {
|
||||||
|
custom: {
|
||||||
|
filterable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
exploreId: ExploreId.left as ExploreId,
|
||||||
|
loading: false,
|
||||||
|
width: 800,
|
||||||
|
onCellFilterAdded: jest.fn(),
|
||||||
|
tableResult: dataFrame,
|
||||||
|
splitOpen: (() => {}) as any,
|
||||||
|
range: {} as any,
|
||||||
|
timeZone: InternalTimeZones.utc,
|
||||||
|
};
|
||||||
|
|
||||||
describe('TableContainer', () => {
|
describe('TableContainer', () => {
|
||||||
it('should render component', () => {
|
it('should render component', () => {
|
||||||
const props = {
|
render(<TableContainer {...defaultProps} />);
|
||||||
exploreId: ExploreId.left as ExploreId,
|
expect(getTable()).toBeInTheDocument();
|
||||||
loading: false,
|
const rows = within(getTable()).getAllByRole('row');
|
||||||
width: 800,
|
expect(rows).toHaveLength(5);
|
||||||
onCellFilterAdded: jest.fn(),
|
expect(getRowsData(rows)).toEqual([
|
||||||
tableResult: {} as DataFrame,
|
{ time: '2021-01-01 00:00:00', text: 'test_string_1' },
|
||||||
splitOpen: (() => {}) as any,
|
{ time: '2021-01-01 03:00:00', text: 'test_string_2' },
|
||||||
range: {} as any,
|
{ time: '2021-01-01 01:00:00', text: 'test_string_3' },
|
||||||
};
|
{ time: '2021-01-01 02:00:00', text: 'test_string_4' },
|
||||||
|
]);
|
||||||
const wrapper = shallow(<TableContainer {...props} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render 0 series returned on no items', () => {
|
it('should render 0 series returned on no items', () => {
|
||||||
const props = {
|
const emptyFrames = {
|
||||||
exploreId: ExploreId.left as ExploreId,
|
name: 'TableResultName',
|
||||||
loading: false,
|
fields: [],
|
||||||
width: 800,
|
length: 0,
|
||||||
onCellFilterAdded: jest.fn(),
|
} as DataFrame;
|
||||||
tableResult: {
|
render(<TableContainer {...defaultProps} tableResult={emptyFrames} />);
|
||||||
name: 'TableResultName',
|
expect(screen.getByText('0 series returned')).toBeInTheDocument();
|
||||||
fields: [],
|
});
|
||||||
length: 0,
|
|
||||||
} as DataFrame,
|
|
||||||
splitOpen: (() => {}) as any,
|
|
||||||
range: {} as any,
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapper = render(<TableContainer {...props} />);
|
it('should update time when timezone changes', () => {
|
||||||
expect(wrapper.find('0 series returned')).toBeTruthy();
|
const { rerender } = render(<TableContainer {...defaultProps} />);
|
||||||
|
const rowsBeforeChange = within(getTable()).getAllByRole('row');
|
||||||
|
expect(getRowsData(rowsBeforeChange)).toEqual([
|
||||||
|
{ time: '2021-01-01 00:00:00', text: 'test_string_1' },
|
||||||
|
{ time: '2021-01-01 03:00:00', text: 'test_string_2' },
|
||||||
|
{ time: '2021-01-01 01:00:00', text: 'test_string_3' },
|
||||||
|
{ time: '2021-01-01 02:00:00', text: 'test_string_4' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
rerender(<TableContainer {...defaultProps} timeZone="cest" />);
|
||||||
|
const rowsAfterChange = within(getTable()).getAllByRole('row');
|
||||||
|
expect(getRowsData(rowsAfterChange)).toEqual([
|
||||||
|
{ time: '2020-12-31 19:00:00', text: 'test_string_1' },
|
||||||
|
{ time: '2020-12-31 22:00:00', text: 'test_string_2' },
|
||||||
|
{ time: '2020-12-31 20:00:00', text: 'test_string_3' },
|
||||||
|
{ time: '2020-12-31 21:00:00', text: 'test_string_4' },
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { connect, ConnectedProps } from 'react-redux';
|
import { connect, ConnectedProps } from 'react-redux';
|
||||||
import { ValueLinkConfig } from '@grafana/data';
|
import { ValueLinkConfig, applyFieldOverrides, TimeZone } from '@grafana/data';
|
||||||
import { Collapse, Table } from '@grafana/ui';
|
import { Collapse, Table } from '@grafana/ui';
|
||||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
@@ -15,6 +15,7 @@ interface TableContainerProps {
|
|||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
width: number;
|
width: number;
|
||||||
|
timeZone: TimeZone;
|
||||||
onCellFilterAdded?: (filter: FilterItem) => void;
|
onCellFilterAdded?: (filter: FilterItem) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,17 +49,27 @@ export class TableContainer extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, onCellFilterAdded, tableResult, width, splitOpen, range, ariaLabel } = this.props;
|
const { loading, onCellFilterAdded, tableResult, width, splitOpen, range, ariaLabel, timeZone } = this.props;
|
||||||
|
|
||||||
const height = this.getTableHeight();
|
const height = this.getTableHeight();
|
||||||
const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER;
|
const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER;
|
||||||
const hasTableResult = tableResult?.length;
|
|
||||||
|
|
||||||
if (tableResult && tableResult.length) {
|
let dataFrame = tableResult;
|
||||||
|
|
||||||
|
if (dataFrame?.length) {
|
||||||
|
dataFrame = applyFieldOverrides({
|
||||||
|
data: [dataFrame],
|
||||||
|
timeZone,
|
||||||
|
theme: config.theme2,
|
||||||
|
replaceVariables: (v: string) => v,
|
||||||
|
fieldConfig: {
|
||||||
|
defaults: {},
|
||||||
|
overrides: [],
|
||||||
|
},
|
||||||
|
})[0];
|
||||||
// Bit of code smell here. We need to add links here to the frame modifying the frame on every render.
|
// Bit of code smell here. We need to add links here to the frame modifying the frame on every render.
|
||||||
// Should work fine in essence but still not the ideal way to pass props. In logs container we do this
|
// Should work fine in essence but still not the ideal way to pass props. In logs container we do this
|
||||||
// differently and sidestep this getLinks API on a dataframe
|
// differently and sidestep this getLinks API on a dataframe
|
||||||
for (const field of tableResult.fields) {
|
for (const field of dataFrame.fields) {
|
||||||
field.getLinks = (config: ValueLinkConfig) => {
|
field.getLinks = (config: ValueLinkConfig) => {
|
||||||
return getFieldLinksForExplore({ field, rowIndex: config.valueRowIndex!, splitOpenFn: splitOpen, range });
|
return getFieldLinksForExplore({ field, rowIndex: config.valueRowIndex!, splitOpenFn: splitOpen, range });
|
||||||
};
|
};
|
||||||
@@ -67,10 +78,10 @@ export class TableContainer extends PureComponent<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse label="Table" loading={loading} isOpen>
|
<Collapse label="Table" loading={loading} isOpen>
|
||||||
{hasTableResult ? (
|
{dataFrame?.length ? (
|
||||||
<Table
|
<Table
|
||||||
ariaLabel={ariaLabel}
|
ariaLabel={ariaLabel}
|
||||||
data={tableResult!}
|
data={dataFrame}
|
||||||
width={tableWidth}
|
width={tableWidth}
|
||||||
height={height}
|
height={height}
|
||||||
onCellFilterAdded={onCellFilterAdded}
|
onCellFilterAdded={onCellFilterAdded}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`TableContainer should render component 1`] = `
|
|
||||||
<Collapse
|
|
||||||
isOpen={true}
|
|
||||||
label="Table"
|
|
||||||
loading={false}
|
|
||||||
>
|
|
||||||
<Memo(MetaInfoText)
|
|
||||||
metaItems={
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"value": "0 series returned",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Collapse>
|
|
||||||
`;
|
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
SelectableValue,
|
SelectableValue,
|
||||||
toCSV,
|
toCSV,
|
||||||
transformDataFrame,
|
transformDataFrame,
|
||||||
|
TimeZone,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { Button, Container, Spinner, Table } from '@grafana/ui';
|
import { Button, Container, Spinner, Table } from '@grafana/ui';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
@@ -30,6 +31,7 @@ import { transformToOTLP } from 'app/plugins/datasource/tempo/resultTransformer'
|
|||||||
interface Props {
|
interface Props {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
options: GetDataOptions;
|
options: GetDataOptions;
|
||||||
|
timeZone: TimeZone;
|
||||||
data?: DataFrame[];
|
data?: DataFrame[];
|
||||||
panel?: PanelModel;
|
panel?: PanelModel;
|
||||||
onOptionsChange?: (options: GetDataOptions) => void;
|
onOptionsChange?: (options: GetDataOptions) => void;
|
||||||
@@ -184,7 +186,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getProcessedData(): DataFrame[] {
|
getProcessedData(): DataFrame[] {
|
||||||
const { options, panel } = this.props;
|
const { options, panel, timeZone } = this.props;
|
||||||
const data = this.state.transformedData;
|
const data = this.state.transformedData;
|
||||||
|
|
||||||
if (!options.withFieldConfig || !panel) {
|
if (!options.withFieldConfig || !panel) {
|
||||||
@@ -197,6 +199,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
|||||||
data,
|
data,
|
||||||
theme: config.theme2,
|
theme: config.theme2,
|
||||||
fieldConfig: panel.fieldConfig,
|
fieldConfig: panel.fieldConfig,
|
||||||
|
timeZone,
|
||||||
replaceVariables: (value: string) => {
|
replaceVariables: (value: string) => {
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user