mirror of
https://github.com/grafana/grafana.git
synced 2025-02-13 00:55:47 -06:00
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
This commit is contained in:
parent
e846a26c8c
commit
0cde7decd7
@ -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}
|
||||
>
|
||||
<div className={drawerStyles.titleWrapper}>
|
||||
<div>{title}</div>
|
||||
<div className={drawerStyles.close} onClick={onClose}>
|
||||
<i className="fa fa-close" />
|
||||
{typeof title === 'string' && (
|
||||
<div className={drawerStyles.titleWrapper}>
|
||||
<div>{title}</div>
|
||||
<div className={drawerStyles.close} onClick={onClose}>
|
||||
<i className="fa fa-close" />
|
||||
</div>
|
||||
</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`
|
||||
|
@ -0,0 +1,101 @@
|
||||
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)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TabsBar>
|
||||
<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%'}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
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()
|
||||
|
Loading…
Reference in New Issue
Block a user