grafana/public/app/features/explore/ExploreQueryInspector.tsx
Piotr Jamróz f97384c2f9
Replace AppEvents with notifyApp in Explore (#31864)
* Replace AppEvents with notifyApp in Explore

* Replace AppEvents with notifyApp in Explore-related components
2021-03-15 15:11:52 +01:00

187 lines
5.8 KiB
TypeScript

import React, { useState } from 'react';
import { Button, JSONFormatter, LoadingPlaceholder, TabbedContainer, TabConfig } from '@grafana/ui';
import { PanelData, TimeZone } from '@grafana/data';
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
import { StoreState, ExploreItemState, ExploreId } from 'app/types';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import { ExploreDrawer } from 'app/features/explore/ExploreDrawer';
import { useEffectOnce } from 'react-use';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { InspectStatsTab } from '../dashboard/components/Inspector/InspectStatsTab';
import { getPanelInspectorStyles } from '../dashboard/components/Inspector/styles';
import { dispatch } from 'app/store/store';
import { notifyApp } from 'app/core/actions';
import { createSuccessNotification } from 'app/core/copy/appNotification';
function stripPropsFromResponse(response: any) {
// ignore silent requests and return early
if (response.config?.hideFromInspector) {
return;
}
const clonedResponse = { ...response }; // clone - dont modify the response
if (clonedResponse.headers) {
delete clonedResponse.headers;
}
if (clonedResponse.config) {
clonedResponse.request = clonedResponse.config;
delete clonedResponse.config;
delete clonedResponse.request.transformRequest;
delete clonedResponse.request.transformResponse;
delete clonedResponse.request.paramSerializer;
delete clonedResponse.request.jsonpCallbackParam;
delete clonedResponse.request.headers;
delete clonedResponse.request.requestId;
delete clonedResponse.request.inspect;
delete clonedResponse.request.retry;
delete clonedResponse.request.timeout;
}
if (clonedResponse.data) {
clonedResponse.response = clonedResponse.data;
delete clonedResponse.config;
delete clonedResponse.data;
delete clonedResponse.status;
delete clonedResponse.statusText;
delete clonedResponse.ok;
delete clonedResponse.url;
delete clonedResponse.redirected;
delete clonedResponse.type;
delete clonedResponse.$$config;
}
return clonedResponse;
}
interface Props {
loading: boolean;
width: number;
exploreId: ExploreId;
queryResponse?: PanelData;
onClose: () => void;
}
export function ExploreQueryInspector(props: Props) {
const [formattedJSON, setFormattedJSON] = useState({});
const getTextForClipboard = () => {
return JSON.stringify(formattedJSON, null, 2);
};
const onClipboardSuccess = () => {
dispatch(notifyApp(createSuccessNotification('Content copied to clipboard')));
};
const [allNodesExpanded, setAllNodesExpanded] = useState(false);
const getOpenNodeCount = () => {
if (allNodesExpanded === null) {
return 3; // 3 is default, ie when state is null
} else if (allNodesExpanded) {
return 20;
}
return 1;
};
const onToggleExpand = () => {
setAllNodesExpanded(!allNodesExpanded);
};
const { loading, width, onClose, queryResponse } = props;
const [response, setResponse] = useState<PanelData>({} as PanelData);
useEffectOnce(() => {
const inspectorStreamSub = getBackendSrv()
.getInspectorStream()
.subscribe((resp) => {
const strippedResponse = stripPropsFromResponse(resp);
if (strippedResponse) {
setResponse(strippedResponse);
}
});
return () => {
inspectorStreamSub?.unsubscribe();
};
});
const haveData = response && Object.keys(response).length > 0;
const styles = getPanelInspectorStyles();
const statsTab: TabConfig = {
label: 'Stats',
value: 'stats',
icon: 'chart-line',
content: <InspectStatsTab data={queryResponse!} timeZone={queryResponse?.request?.timezone as TimeZone} />,
};
const inspectorTab: TabConfig = {
label: 'Query Inspector',
value: 'query_inspector',
icon: 'info-circle',
content: (
<>
<div className={styles.toolbar}>
{haveData && (
<>
<Button
icon={allNodesExpanded ? 'minus' : 'plus'}
variant="secondary"
className={styles.toolbarItem}
onClick={onToggleExpand}
>
{allNodesExpanded ? 'Collapse' : 'Expand'} all
</Button>
<CopyToClipboard
text={getTextForClipboard}
onSuccess={onClipboardSuccess}
elType="div"
className={styles.toolbarItem}
>
<Button icon="copy" variant="secondary">
Copy to clipboard
</Button>
</CopyToClipboard>
</>
)}
<div className="flex-grow-1" />
</div>
<div className={styles.contentQueryInspector}>
{loading && <LoadingPlaceholder text="Loading query inspector..." />}
{!loading && haveData && (
<JSONFormatter json={response!} open={getOpenNodeCount()} onDidRender={setFormattedJSON} />
)}
{!loading && !haveData && (
<p className="muted">No request & response collected yet. Run query to collect request & response.</p>
)}
</div>
</>
),
};
const tabs = [statsTab, inspectorTab];
return (
<ExploreDrawer width={width} onResize={() => {}}>
<TabbedContainer tabs={tabs} onClose={onClose} closeIconTooltip="Close query inspector" />
</ExploreDrawer>
);
}
function mapStateToProps(state: StoreState, { exploreId }: { exploreId: ExploreId }) {
const explore = state.explore;
const item: ExploreItemState = explore[exploreId]!;
const { loading, queryResponse } = item;
return {
loading,
queryResponse,
};
}
export default hot(module)(connect(mapStateToProps)(ExploreQueryInspector));