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'; ...@@ -8,7 +8,7 @@ import { stylesFactory, useTheme, selectThemeVariant } from '../../themes';
export interface Props { export interface Props {
children: ReactNode; children: ReactNode;
/** Title shown at the top of the drawer */ /** Title shown at the top of the drawer */
title?: string; title?: (() => JSX.Element) | string;
/** Should the Drawer be closable by clicking on the mask */ /** Should the Drawer be closable by clicking on the mask */
closeOnMaskClick?: boolean; closeOnMaskClick?: boolean;
/** Render the drawer inside a container on the page */ /** Render the drawer inside a container on the page */
...@@ -43,7 +43,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, scollableContent: boolean) ...@@ -43,7 +43,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme, scollableContent: boolean)
titleWrapper: css` titleWrapper: css`
font-size: ${theme.typography.size.lg}; font-size: ${theme.typography.size.lg};
display: flex; display: flex;
align-items: center; align-items: baseline;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid ${borderColor}; border-bottom: 1px solid ${borderColor};
padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md}; padding: ${theme.spacing.sm} 0 ${theme.spacing.sm} ${theme.spacing.md};
...@@ -94,12 +94,15 @@ export const Drawer: FC<Props> = ({ ...@@ -94,12 +94,15 @@ export const Drawer: FC<Props> = ({
style={{ position: `${inline && 'absolute'}` } as CSSProperties} style={{ position: `${inline && 'absolute'}` } as CSSProperties}
className={drawerStyles.drawer} className={drawerStyles.drawer}
> >
{typeof title === 'string' && (
<div className={drawerStyles.titleWrapper}> <div className={drawerStyles.titleWrapper}>
<div>{title}</div> <div>{title}</div>
<div className={drawerStyles.close} onClick={onClose}> <div className={drawerStyles.close} onClick={onClose}>
<i className="fa fa-close" /> <i className="fa fa-close" />
</div> </div>
</div> </div>
)}
{typeof title === 'function' && title()}
<div className={drawerStyles.content}> <div className={drawerStyles.content}>
{!scrollableContent ? children : <CustomScrollbar>{children}</CustomScrollbar>} {!scrollableContent ? children : <CustomScrollbar>{children}</CustomScrollbar>}
</div> </div>
......
...@@ -35,6 +35,7 @@ export const getTableStyles = stylesFactory( ...@@ -35,6 +35,7 @@ export const getTableStyles = stylesFactory(
border-spacing: 0; border-spacing: 0;
`, `,
thead: css` thead: css`
label: thead;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
background: ${headerBg}; background: ${headerBg};
...@@ -46,6 +47,7 @@ export const getTableStyles = stylesFactory( ...@@ -46,6 +47,7 @@ export const getTableStyles = stylesFactory(
color: ${colors.blue}; color: ${colors.blue};
`, `,
row: css` row: css`
label: row;
border-bottom: 2px solid ${colors.bodyBg}; border-bottom: 2px solid ${colors.bodyBg};
`, `,
tableCell: css` 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'; ...@@ -3,19 +3,10 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { css } from 'emotion'; import { css } from 'emotion';
import { InspectHeader } from './InspectHeader';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { import { JSONFormatter, Drawer, Select, Table, TabContent, Forms, stylesFactory, CustomScrollbar } from '@grafana/ui';
JSONFormatter,
Drawer,
Select,
Table,
TabsBar,
Tab,
TabContent,
Forms,
stylesFactory,
CustomScrollbar,
} from '@grafana/ui';
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime'; import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
import { import {
DataFrame, DataFrame,
...@@ -44,7 +35,7 @@ export enum InspectTab { ...@@ -44,7 +35,7 @@ export enum InspectTab {
interface State { interface State {
// The last raw response // The last raw response
last?: PanelData; last: PanelData;
// Data frem the last response // Data frem the last response
data: DataFrame[]; data: DataFrame[];
...@@ -57,6 +48,10 @@ interface State { ...@@ -57,6 +48,10 @@ interface State {
// If the datasource supports custom metadata // If the datasource supports custom metadata
metaDS?: DataSourceApi; metaDS?: DataSourceApi;
stats: { requestTime: number; queries: number; dataSources: number };
drawerWidth: string;
} }
const getStyles = stylesFactory(() => { const getStyles = stylesFactory(() => {
...@@ -89,9 +84,12 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -89,9 +84,12 @@ export class PanelInspector extends PureComponent<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
last: {} as PanelData,
data: [], data: [],
selected: 0, selected: 0,
tab: props.selectedTab || InspectTab.Data, 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> { ...@@ -110,8 +108,14 @@ export class PanelInspector extends PureComponent<Props, State> {
// Find the first DataSource wanting to show custom metadata // Find the first DataSource wanting to show custom metadata
let metaDS: DataSourceApi; let metaDS: DataSourceApi;
const data = lastResult?.series; const data = lastResult.series;
const error = lastResult?.error; 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) { if (data) {
for (const frame of data) { for (const frame of data) {
...@@ -132,6 +136,11 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -132,6 +136,11 @@ export class PanelInspector extends PureComponent<Props, State> {
data, data,
metaDS, metaDS,
tab: error ? InspectTab.Error : prevState.tab, tab: error ? InspectTab.Error : prevState.tab,
stats: {
requestTime,
queries,
dataSources,
},
})); }));
} }
...@@ -142,6 +151,12 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -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>) => { onSelectTab = (item: SelectableValue<InspectTab>) => {
this.setState({ tab: item.value || InspectTab.Data }); this.setState({ tab: item.value || InspectTab.Data });
}; };
...@@ -261,17 +276,9 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -261,17 +276,9 @@ export class PanelInspector extends PureComponent<Props, State> {
); );
} }
render() { drawerHeader = () => {
const { panel } = this.props; const { tab, last, stats } = this.state;
const { last, tab } = this.state;
const styles = getStyles();
const error = last?.error; const error = last?.error;
if (!panel) {
this.onDismiss(); // Try to close the component
return null;
}
const tabs = []; const tabs = [];
if (last && last?.series?.length > 0) { if (last && last?.series?.length > 0) {
tabs.push({ label: 'Data', value: InspectTab.Data }); tabs.push({ label: 'Data', value: InspectTab.Data });
...@@ -285,19 +292,26 @@ export class PanelInspector extends PureComponent<Props, State> { ...@@ -285,19 +292,26 @@ export class PanelInspector extends PureComponent<Props, State> {
tabs.push({ label: 'Raw JSON', value: InspectTab.Raw }); tabs.push({ label: 'Raw JSON', value: InspectTab.Raw });
return ( return (
<Drawer title={panel.title} onClose={this.onDismiss}> <InspectHeader
<TabsBar> tabs={tabs}
{tabs.map((t, index) => { tab={tab}
return ( stats={stats}
<Tab onSelectTab={this.onSelectTab}
key={`${t.value}-${index}`} onClose={this.onDismiss}
label={t.label} panel={this.props.panel}
active={t.value === tab} onToggleExpand={this.onToggleExpand}
onChangeTab={() => this.onSelectTab(t)} 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}> <TabContent className={styles.tabContent}>
{tab === InspectTab.Data ? ( {tab === InspectTab.Data ? (
this.renderDataTab() 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