Commit 0cde7dec by Peter Holmberg Committed by GitHub

Inspect: Inspect header design update (#22120)

* first things

* rename

* add stats, remove table related changes

* header size and expand icon

* add case for nan request time

* fixing null checks

* reverting changes to table

* fix background on header

* Some small fixes after review

* do not use formInputBg
parent e846a26c
......@@ -8,7 +8,7 @@ import { stylesFactory, useTheme, selectThemeVariant } from '../../themes';
export interface Props {
children: ReactNode;
/** Title shown at the top of the drawer */
title?: string;
title?: (() => JSX.Element) | string;
/** Should the Drawer be closable by clicking on the mask */
closeOnMaskClick?: boolean;
/** Render the drawer inside a container on the page */
......@@ -43,7 +43,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, scollableContent: boolean)
titleWrapper: css`
font-size: ${theme.typography.size.lg};
display: flex;
align-items: center;
align-items: baseline;
justify-content: space-between;
border-bottom: 1px solid ${borderColor};
padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md};
......@@ -94,12 +94,15 @@ export const Drawer: FC<Props> = ({
style={{ position: `${inline && 'absolute'}` } as CSSProperties}
className={drawerStyles.drawer}
>
{typeof title === 'string' && (
<div className={drawerStyles.titleWrapper}>
<div>{title}</div>
<div className={drawerStyles.close} onClick={onClose}>
<i className="fa fa-close" />
</div>
</div>
)}
{typeof title === 'function' && title()}
<div className={drawerStyles.content}>
{!scrollableContent ? children : <CustomScrollbar>{children}</CustomScrollbar>}
</div>
......
......@@ -35,6 +35,7 @@ export const getTableStyles = stylesFactory(
border-spacing: 0;
`,
thead: css`
label: thead;
overflow-y: auto;
overflow-x: hidden;
background: ${headerBg};
......@@ -46,6 +47,7 @@ export const getTableStyles = stylesFactory(
color: ${colors.blue};
`,
row: css`
label: row;
border-bottom: 2px solid ${colors.bodyBg};
`,
tableCell: css`
......
import React, { FC } from 'react';
import { css } from 'emotion';
import { Icon, selectThemeVariant, stylesFactory, Tab, TabsBar, useTheme } from '@grafana/ui';
import { GrafanaTheme, SelectableValue } from '@grafana/data';
import { InspectTab } from './PanelInspector';
import { PanelModel } from '../../state';
interface Props {
tab: InspectTab;
tabs: Array<{ label: string; value: InspectTab }>;
stats: { requestTime: number; queries: number; dataSources: number };
panel: PanelModel;
isExpanded: boolean;
onSelectTab: (tab: SelectableValue<InspectTab>) => void;
onClose: () => void;
onToggleExpand: () => void;
}
export const InspectHeader: FC<Props> = ({
tab,
tabs,
onSelectTab,
onClose,
onToggleExpand,
panel,
stats,
isExpanded,
}) => {
const theme = useTheme();
const styles = getStyles(theme);
return (
<div className={styles.header}>
<div className={styles.actions}>
<div className={styles.iconWrapper} onClick={onToggleExpand}>
<Icon name={isExpanded ? 'chevron-right' : 'chevron-left'} className={styles.icon} />
</div>
<div className={styles.iconWrapper} onClick={onClose}>
<Icon name="times" className={styles.icon} />
</div>
</div>
<div className={styles.titleWrapper}>
<h3>{panel.title}</h3>
<div>{formatStats(stats)}</div>
</div>
<TabsBar>
{tabs.map((t, index) => {
return (
<Tab
key={`${t.value}-${index}`}
label={t.label}
active={t.value === tab}
onChangeTab={() => onSelectTab(t)}
/>
);
})}
</TabsBar>
</div>
);
};
const getStyles = stylesFactory((theme: GrafanaTheme) => {
const headerBackground = selectThemeVariant({ dark: theme.colors.gray15, light: theme.colors.white }, theme.type);
return {
header: css`
background-color: ${headerBackground};
z-index: 1;
flex-grow: 0;
padding: ${theme.spacing.sm} ${theme.spacing.sm} 0 ${theme.spacing.lg};
`,
actions: css`
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: ${theme.spacing.md};
`,
iconWrapper: css`
cursor: pointer;
width: 25px;
height: 100%;
display: flex;
flex-shrink: 0;
justify-content: center;
`,
icon: css`
font-size: ${theme.typography.size.lg};
`,
titleWrapper: css`
margin-bottom: ${theme.spacing.lg};
`,
};
});
function formatStats(stats: { requestTime: number; queries: number; dataSources: number }) {
const queries = `${stats.queries} ${stats.queries === 1 ? 'query' : 'queries'}`;
const dataSources = `${stats.dataSources} ${stats.dataSources === 1 ? 'data source' : 'data sources'}`;
const requestTime = `${stats.requestTime === -1 ? 'N/A' : stats.requestTime}ms`;
return `${queries} - ${dataSources} - ${requestTime}`;
}
......@@ -3,19 +3,10 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { saveAs } from 'file-saver';
import { css } from 'emotion';
import { InspectHeader } from './InspectHeader';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import {
JSONFormatter,
Drawer,
Select,
Table,
TabsBar,
Tab,
TabContent,
Forms,
stylesFactory,
CustomScrollbar,
} from '@grafana/ui';
import { JSONFormatter, Drawer, Select, Table, TabContent, Forms, stylesFactory, CustomScrollbar } from '@grafana/ui';
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
import {
DataFrame,
......@@ -44,7 +35,7 @@ export enum InspectTab {
interface State {
// The last raw response
last?: PanelData;
last: PanelData;
// Data frem the last response
data: DataFrame[];
......@@ -57,6 +48,10 @@ interface State {
// If the datasource supports custom metadata
metaDS?: DataSourceApi;
stats: { requestTime: number; queries: number; dataSources: number };
drawerWidth: string;
}
const getStyles = stylesFactory(() => {
......@@ -89,9 +84,12 @@ export class PanelInspector extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
last: {} as PanelData,
data: [],
selected: 0,
tab: props.selectedTab || InspectTab.Data,
drawerWidth: '40%',
stats: { requestTime: 0, queries: 0, dataSources: 0 },
};
}
......@@ -110,8 +108,14 @@ export class PanelInspector extends PureComponent<Props, State> {
// Find the first DataSource wanting to show custom metadata
let metaDS: DataSourceApi;
const data = lastResult?.series;
const error = lastResult?.error;
const data = lastResult.series;
const error = lastResult.error;
const targets = lastResult.request?.targets;
const requestTime = lastResult.request?.endTime ? lastResult.request?.endTime - lastResult.request.startTime : -1;
const queries = targets ? targets.length : 0;
const dataSources = new Set(targets.map(t => t.datasource)).size;
if (data) {
for (const frame of data) {
......@@ -132,6 +136,11 @@ export class PanelInspector extends PureComponent<Props, State> {
data,
metaDS,
tab: error ? InspectTab.Error : prevState.tab,
stats: {
requestTime,
queries,
dataSources,
},
}));
}
......@@ -142,6 +151,12 @@ export class PanelInspector extends PureComponent<Props, State> {
});
};
onToggleExpand = () => {
this.setState(prevState => ({
drawerWidth: prevState.drawerWidth === '100%' ? '40%' : '100%',
}));
};
onSelectTab = (item: SelectableValue<InspectTab>) => {
this.setState({ tab: item.value || InspectTab.Data });
};
......@@ -261,17 +276,9 @@ export class PanelInspector extends PureComponent<Props, State> {
);
}
render() {
const { panel } = this.props;
const { last, tab } = this.state;
const styles = getStyles();
drawerHeader = () => {
const { tab, last, stats } = this.state;
const error = last?.error;
if (!panel) {
this.onDismiss(); // Try to close the component
return null;
}
const tabs = [];
if (last && last?.series?.length > 0) {
tabs.push({ label: 'Data', value: InspectTab.Data });
......@@ -285,19 +292,26 @@ export class PanelInspector extends PureComponent<Props, State> {
tabs.push({ label: 'Raw JSON', value: InspectTab.Raw });
return (
<Drawer title={panel.title} onClose={this.onDismiss}>
<TabsBar>
{tabs.map((t, index) => {
return (
<Tab
key={`${t.value}-${index}`}
label={t.label}
active={t.value === tab}
onChangeTab={() => this.onSelectTab(t)}
<InspectHeader
tabs={tabs}
tab={tab}
stats={stats}
onSelectTab={this.onSelectTab}
onClose={this.onDismiss}
panel={this.props.panel}
onToggleExpand={this.onToggleExpand}
isExpanded={this.state.drawerWidth === '100%'}
/>
);
})}
</TabsBar>
};
render() {
const { last, tab, drawerWidth } = this.state;
const styles = getStyles();
const error = last?.error;
return (
<Drawer title={this.drawerHeader} width={drawerWidth} onClose={this.onDismiss}>
<TabContent className={styles.tabContent}>
{tab === InspectTab.Data ? (
this.renderDataTab()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment