Explore: Fix timezone not changing for graph and table (#42430)

* 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>
pull/42636/head
Ivana Huckova 4 years ago committed by GitHub
parent feea6e456c
commit d28e7b6185
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      public/app/features/dashboard/components/Inspector/InspectContent.tsx
  2. 5
      public/app/features/explore/Explore.tsx
  3. 1
      public/app/features/explore/ExploreGraph.tsx
  4. 3
      public/app/features/explore/ExploreQueryInspector.test.tsx
  5. 4
      public/app/features/explore/ExploreQueryInspector.tsx
  6. 121
      public/app/features/explore/TableContainer.test.tsx
  7. 27
      public/app/features/explore/TableContainer.tsx
  8. 19
      public/app/features/explore/__snapshots__/TableContainer.test.tsx.snap
  9. 5
      public/app/features/inspector/InspectDataTab.tsx

@ -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, it('should update time when timezone changes', () => {
splitOpen: (() => {}) as any, const { rerender } = render(<TableContainer {...defaultProps} />);
range: {} as any, const rowsBeforeChange = within(getTable()).getAllByRole('row');
}; expect(getRowsData(rowsBeforeChange)).toEqual([
{ time: '2021-01-01 00:00:00', text: 'test_string_1' },
const wrapper = render(<TableContainer {...props} />); { time: '2021-01-01 03:00:00', text: 'test_string_2' },
expect(wrapper.find('0 series returned')).toBeTruthy(); { 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;
}, },

Loading…
Cancel
Save