Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd7e052723 | |||
| b1057b3cbf |
@@ -599,6 +599,7 @@ export {
|
||||
type PluginExtensionResourceAttributesContext,
|
||||
type CentralAlertHistorySceneV1Props,
|
||||
} from './types/pluginExtensions';
|
||||
export { type PrometheusQueryResultsV1Props } from './types/exposedComponentProps';
|
||||
export {
|
||||
type ScopeDashboardBindingSpec,
|
||||
type ScopeDashboardBindingStatus,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { LoadingState } from './data';
|
||||
import { DataFrame } from './dataFrame';
|
||||
import { DataLinkPostProcessor } from './fieldOverrides';
|
||||
import { TimeZone } from './time';
|
||||
|
||||
/**
|
||||
* Props for the PrometheusQueryResults exposed component.
|
||||
* @see PluginExtensionExposedComponents.PrometheusQueryResultsV1
|
||||
*/
|
||||
export type PrometheusQueryResultsV1Props = {
|
||||
/** Raw DataFrames to display (processing handled internally). Defaults to empty array. */
|
||||
tableResult?: DataFrame[];
|
||||
/** Width of the container in pixels. Defaults to 800. */
|
||||
width?: number;
|
||||
/** Timezone for value formatting. Defaults to 'browser'. */
|
||||
timeZone?: TimeZone;
|
||||
/** Loading state for panel chrome indicator */
|
||||
loading?: LoadingState;
|
||||
/** Aria label for accessibility */
|
||||
ariaLabel?: string;
|
||||
/** Start in Raw view instead of Table view. When true, shows toggle. */
|
||||
showRawPrometheus?: boolean;
|
||||
/** Callback when user adds a cell filter */
|
||||
onCellFilterAdded?: (filter: { key: string; value: string; operator: '=' | '!=' }) => void;
|
||||
/** Optional post-processor for data links (used by Explore for split view) */
|
||||
dataLinkPostProcessor?: DataLinkPostProcessor;
|
||||
};
|
||||
@@ -245,6 +245,7 @@ export enum PluginExtensionPointPatterns {
|
||||
export enum PluginExtensionExposedComponents {
|
||||
CentralAlertHistorySceneV1 = 'grafana/central-alert-history-scene/v1',
|
||||
AddToDashboardFormV1 = 'grafana/add-to-dashboard-form/v1',
|
||||
PrometheusQueryResultsV1 = 'grafana/prometheus-query-results/v1',
|
||||
}
|
||||
|
||||
export type PluginExtensionPanelContext = {
|
||||
|
||||
+35
-12
@@ -1,10 +1,9 @@
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
|
||||
import { FieldType, getDefaultTimeRange, InternalTimeZones, toDataFrame, LoadingState } from '@grafana/data';
|
||||
import { FieldType, InternalTimeZones, toDataFrame, LoadingState } from '@grafana/data';
|
||||
import { getTemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { TABLE_RESULTS_STYLE } from 'app/types/explore';
|
||||
|
||||
import { RawPrometheusContainer } from './RawPrometheusContainer';
|
||||
import { PrometheusQueryResultsContainer } from './PrometheusQueryResultsContainer';
|
||||
|
||||
function getTable(): HTMLElement {
|
||||
return screen.getAllByRole('table')[0];
|
||||
@@ -52,27 +51,30 @@ const dataFrame = toDataFrame({
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
exploreId: 'left',
|
||||
loading: LoadingState.NotStarted,
|
||||
width: 800,
|
||||
onCellFilterAdded: jest.fn(),
|
||||
tableResult: [dataFrame],
|
||||
splitOpenFn: () => {},
|
||||
range: getDefaultTimeRange(),
|
||||
timeZone: InternalTimeZones.utc,
|
||||
resultsStyle: TABLE_RESULTS_STYLE.raw,
|
||||
showRawPrometheus: false,
|
||||
};
|
||||
|
||||
describe('RawPrometheusContainer', () => {
|
||||
describe('PrometheusQueryResultsContainer', () => {
|
||||
beforeAll(() => {
|
||||
getTemplateSrv();
|
||||
});
|
||||
|
||||
it('should render component for prometheus', () => {
|
||||
render(<RawPrometheusContainer {...defaultProps} showRawPrometheus={true} />);
|
||||
it('should render table with data and toggle when showRawPrometheus is true', async () => {
|
||||
render(<PrometheusQueryResultsContainer {...defaultProps} showRawPrometheus={true} />);
|
||||
|
||||
// Wait for lazy-loaded component to render
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByRole('table').length).toBe(1);
|
||||
});
|
||||
|
||||
// Toggle should be visible
|
||||
expect(screen.queryAllByRole('radio').length).toBeGreaterThan(0);
|
||||
|
||||
expect(screen.queryAllByRole('table').length).toBe(1);
|
||||
fireEvent.click(getTableToggle());
|
||||
|
||||
expect(getTable()).toBeInTheDocument();
|
||||
@@ -85,4 +87,25 @@ describe('RawPrometheusContainer', () => {
|
||||
{ time: '2021-01-01 02:00:00', text: 'test_string_4' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should render table without toggle when showRawPrometheus is false', async () => {
|
||||
render(<PrometheusQueryResultsContainer {...defaultProps} showRawPrometheus={false} />);
|
||||
|
||||
// Wait for lazy-loaded component to render
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByRole('table').length).toBe(1);
|
||||
});
|
||||
|
||||
// Toggle should NOT be visible
|
||||
expect(screen.queryAllByRole('radio').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should render empty state when no data', async () => {
|
||||
render(<PrometheusQueryResultsContainer {...defaultProps} tableResult={[]} showRawPrometheus={true} />);
|
||||
|
||||
// Wait for lazy-loaded component to render
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('0 series returned')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { lazy, Suspense, useMemo } from 'react';
|
||||
|
||||
import { applyFieldOverrides, PrometheusQueryResultsV1Props } from '@grafana/data';
|
||||
import { config, getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
const RawPrometheusContainerPureLazy = lazy(() =>
|
||||
import('./RawPrometheusContainerPure').then((m) => ({ default: m.RawPrometheusContainerPure }))
|
||||
);
|
||||
|
||||
/**
|
||||
* EXPOSED COMPONENT (stable): grafana/prometheus-query-results/v1
|
||||
*
|
||||
* This component is exposed to plugins via the Plugin Extensions system.
|
||||
* Treat its props and user-visible behavior as a stable contract. Do not make
|
||||
* breaking changes in-place. If you need to change the API or behavior in a
|
||||
* breaking way, create a new versioned component (e.g. PrometheusQueryResultsV2)
|
||||
* and register it under a new ID: "grafana/prometheus-query-results/v2".
|
||||
*
|
||||
* Displays Prometheus query results with Table/Raw toggle.
|
||||
* Pass raw DataFrames - processing (applyFieldOverrides) is handled internally.
|
||||
*
|
||||
* Example usage in a plugin:
|
||||
* ```typescript
|
||||
* import { usePluginComponent } from '@grafana/runtime';
|
||||
* import { PluginExtensionExposedComponents } from '@grafana/data';
|
||||
*
|
||||
* const { component: PrometheusQueryResults } = usePluginComponent(
|
||||
* PluginExtensionExposedComponents.PrometheusQueryResultsV1
|
||||
* );
|
||||
*
|
||||
* // Render - just pass raw data
|
||||
* <PrometheusQueryResults tableResult={rawDataFrames} width={800} timeZone="browser" />
|
||||
* ```
|
||||
*/
|
||||
export const PrometheusQueryResultsContainer = (props: PrometheusQueryResultsV1Props) => {
|
||||
const width = props.width ?? 800;
|
||||
const timeZone = props.timeZone ?? 'browser';
|
||||
|
||||
// Memoize cloneDeep + applyFieldOverrides to avoid expensive operations on every render
|
||||
// cloneDeep is needed to avoid mutating frozen props from plugin extension system
|
||||
const processedData = useMemo(() => {
|
||||
const tableResult = props.tableResult ?? [];
|
||||
const cloned = cloneDeep(tableResult);
|
||||
if (cloned?.length) {
|
||||
return applyFieldOverrides({
|
||||
data: cloned,
|
||||
timeZone,
|
||||
theme: config.theme2,
|
||||
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
|
||||
fieldConfig: { defaults: {}, overrides: [] },
|
||||
dataLinkPostProcessor: props.dataLinkPostProcessor,
|
||||
});
|
||||
}
|
||||
return cloned;
|
||||
}, [props.tableResult, timeZone, props.dataLinkPostProcessor]);
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<RawPrometheusContainerPureLazy
|
||||
tableResult={processedData}
|
||||
width={width}
|
||||
loading={props.loading}
|
||||
ariaLabel={props.ariaLabel}
|
||||
showRawPrometheus={props.showRawPrometheus}
|
||||
onCellFilterAdded={props.onCellFilterAdded}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
@@ -1,31 +1,31 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { memo, useState } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { applyFieldOverrides, DataFrame, SelectableValue, SplitOpen } from '@grafana/data';
|
||||
import { getTemplateSrv, reportInteraction } from '@grafana/runtime';
|
||||
import { DataFrame, SplitOpen } from '@grafana/data';
|
||||
import { TimeZone } from '@grafana/schema';
|
||||
import { RadioButtonGroup, Table, AdHocFilterItem, PanelChrome } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { PANEL_BORDER } from 'app/core/constants';
|
||||
import { ExploreItemState, TABLE_RESULTS_STYLE, TABLE_RESULTS_STYLES, TableResultsStyle } from 'app/types/explore';
|
||||
import { AdHocFilterItem } from '@grafana/ui';
|
||||
import { ExploreItemState } from 'app/types/explore';
|
||||
import { StoreState } from 'app/types/store';
|
||||
|
||||
import { MetaInfoText } from '../MetaInfoText';
|
||||
import RawListContainer from '../PrometheusListView/RawListContainer';
|
||||
import { exploreDataLinkPostProcessorFactory } from '../utils/links';
|
||||
|
||||
interface RawPrometheusContainerProps {
|
||||
import { PrometheusQueryResultsContainer } from './PrometheusQueryResultsContainer';
|
||||
|
||||
// ============================================================================
|
||||
// Redux-connected Component - Used by Explore
|
||||
// ============================================================================
|
||||
|
||||
interface ExploreRawPrometheusContainerProps {
|
||||
ariaLabel?: string;
|
||||
exploreId: string;
|
||||
width: number;
|
||||
timeZone: TimeZone;
|
||||
onCellFilterAdded?: (filter: AdHocFilterItem) => void;
|
||||
showRawPrometheus?: boolean;
|
||||
splitOpenFn: SplitOpen;
|
||||
splitOpenFn?: SplitOpen;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: StoreState, { exploreId }: RawPrometheusContainerProps) {
|
||||
function mapStateToProps(state: StoreState, { exploreId }: ExploreRawPrometheusContainerProps) {
|
||||
const explore = state.explore;
|
||||
const item: ExploreItemState = explore.panes[exploreId]!;
|
||||
const { rawPrometheusResult, range, queryResponse } = item;
|
||||
@@ -37,121 +37,47 @@ function mapStateToProps(state: StoreState, { exploreId }: RawPrometheusContaine
|
||||
|
||||
const connector = connect(mapStateToProps, {});
|
||||
|
||||
type Props = RawPrometheusContainerProps & ConnectedProps<typeof connector>;
|
||||
type ExploreProps = ExploreRawPrometheusContainerProps & ConnectedProps<typeof connector>;
|
||||
|
||||
export const RawPrometheusContainer = memo(
|
||||
/**
|
||||
* Redux-connected wrapper for Explore.
|
||||
* Gets data from Redux and passes to PrometheusQueryResultsContainer for processing and display.
|
||||
*/
|
||||
const ExploreRawPrometheusContainer = memo(
|
||||
({
|
||||
loading,
|
||||
onCellFilterAdded,
|
||||
tableResult,
|
||||
width,
|
||||
splitOpenFn,
|
||||
range,
|
||||
ariaLabel,
|
||||
timeZone,
|
||||
showRawPrometheus,
|
||||
}: Props) => {
|
||||
// If resultsStyle is undefined we won't render the toggle, and the default table will be rendered
|
||||
const [resultsStyle, setResultsStyle] = useState<TableResultsStyle | undefined>(
|
||||
showRawPrometheus ? TABLE_RESULTS_STYLE.raw : undefined
|
||||
range,
|
||||
splitOpenFn,
|
||||
}: ExploreProps) => {
|
||||
const dataLinkPostProcessor = useMemo(
|
||||
() => exploreDataLinkPostProcessorFactory(splitOpenFn, range),
|
||||
[splitOpenFn, range]
|
||||
);
|
||||
|
||||
const onChangeResultsStyle = (newResultsStyle: TableResultsStyle) => {
|
||||
setResultsStyle(newResultsStyle);
|
||||
};
|
||||
|
||||
const getTableHeight = () => {
|
||||
if (!tableResult || tableResult.length === 0) {
|
||||
return 200;
|
||||
}
|
||||
|
||||
// tries to estimate table height
|
||||
return Math.max(Math.min(600, tableResult[0].length * 35) + 35);
|
||||
};
|
||||
|
||||
const renderLabel = () => {
|
||||
const spacing = css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
flex: '1',
|
||||
});
|
||||
const ALL_GRAPH_STYLE_OPTIONS: Array<SelectableValue<TableResultsStyle>> = TABLE_RESULTS_STYLES.map((style) => ({
|
||||
value: style,
|
||||
// capital-case it and switch `_` to ` `
|
||||
label: style[0].toUpperCase() + style.slice(1).replace(/_/, ' '),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={spacing}>
|
||||
<RadioButtonGroup
|
||||
onClick={() => {
|
||||
const props = {
|
||||
state: resultsStyle === TABLE_RESULTS_STYLE.table ? TABLE_RESULTS_STYLE.raw : TABLE_RESULTS_STYLE.table,
|
||||
};
|
||||
reportInteraction('grafana_explore_prometheus_instant_query_ui_toggle_clicked', props);
|
||||
}}
|
||||
size="sm"
|
||||
options={ALL_GRAPH_STYLE_OPTIONS}
|
||||
value={resultsStyle}
|
||||
onChange={onChangeResultsStyle}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const height = getTableHeight();
|
||||
const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER;
|
||||
|
||||
let dataFrames = tableResult;
|
||||
|
||||
const dataLinkPostProcessor = exploreDataLinkPostProcessorFactory(splitOpenFn, range);
|
||||
|
||||
if (dataFrames?.length) {
|
||||
dataFrames = applyFieldOverrides({
|
||||
data: dataFrames,
|
||||
timeZone,
|
||||
theme: config.theme2,
|
||||
replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
|
||||
fieldConfig: {
|
||||
defaults: {},
|
||||
overrides: [],
|
||||
},
|
||||
dataLinkPostProcessor,
|
||||
});
|
||||
}
|
||||
|
||||
const frames = dataFrames?.filter(
|
||||
(frame: DataFrame | undefined): frame is DataFrame => !!frame && frame.length !== 0
|
||||
);
|
||||
|
||||
const title = resultsStyle === TABLE_RESULTS_STYLE.raw ? 'Raw' : 'Table';
|
||||
const label = resultsStyle !== undefined ? renderLabel() : 'Table';
|
||||
|
||||
// Render table as default if resultsStyle is not set.
|
||||
const renderTable = !resultsStyle || resultsStyle === TABLE_RESULTS_STYLE.table;
|
||||
|
||||
return (
|
||||
<PanelChrome title={title} actions={label} loadingState={loading}>
|
||||
{frames?.length && (
|
||||
<>
|
||||
{renderTable && (
|
||||
<Table
|
||||
ariaLabel={ariaLabel}
|
||||
data={frames[0]}
|
||||
width={tableWidth}
|
||||
height={height}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
/>
|
||||
)}
|
||||
{resultsStyle === TABLE_RESULTS_STYLE.raw && <RawListContainer tableResult={frames[0]} />}
|
||||
</>
|
||||
)}
|
||||
{!frames?.length && <MetaInfoText metaItems={[{ value: '0 series returned' }]} />}
|
||||
</PanelChrome>
|
||||
<PrometheusQueryResultsContainer
|
||||
tableResult={tableResult}
|
||||
width={width}
|
||||
timeZone={timeZone}
|
||||
loading={loading}
|
||||
ariaLabel={ariaLabel}
|
||||
showRawPrometheus={showRawPrometheus}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
dataLinkPostProcessor={dataLinkPostProcessor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RawPrometheusContainer.displayName = 'RawPrometheusContainer';
|
||||
ExploreRawPrometheusContainer.displayName = 'ExploreRawPrometheusContainer';
|
||||
|
||||
export default connector(RawPrometheusContainer);
|
||||
// Keep the old export name for backwards compatibility
|
||||
export const RawPrometheusContainer = ExploreRawPrometheusContainer;
|
||||
|
||||
export default connector(ExploreRawPrometheusContainer);
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
import { DataFrame, GrafanaTheme2, LoadingState, SelectableValue } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { RadioButtonGroup, Table, AdHocFilterItem, PanelChrome, useStyles2 } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { PANEL_BORDER } from 'app/core/constants';
|
||||
import { TABLE_RESULTS_STYLE, TABLE_RESULTS_STYLES, TableResultsStyle } from 'app/types/explore';
|
||||
|
||||
import { MetaInfoText } from '../MetaInfoText';
|
||||
import RawListContainer from '../PrometheusListView/RawListContainer';
|
||||
|
||||
const getStyles = (_theme: GrafanaTheme2) => ({
|
||||
spacing: css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
flex: '1',
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Props for the pure RawPrometheusContainer component.
|
||||
* This component expects pre-processed DataFrames (caller should apply applyFieldOverrides).
|
||||
*/
|
||||
export interface RawPrometheusContainerPureProps {
|
||||
/** Pre-processed DataFrames to display */
|
||||
tableResult: DataFrame[];
|
||||
/** Width of the container in pixels */
|
||||
width: number;
|
||||
/** Loading state for panel chrome indicator */
|
||||
loading?: LoadingState;
|
||||
/** Aria label for accessibility */
|
||||
ariaLabel?: string;
|
||||
/** Start in Raw view instead of Table view. When true, shows toggle. When false/undefined, shows table only. */
|
||||
showRawPrometheus?: boolean;
|
||||
/** Callback when user adds a cell filter */
|
||||
onCellFilterAdded?: (filter: AdHocFilterItem) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure component for displaying Prometheus query results with Table/Raw toggle.
|
||||
* This component does NOT connect to Redux and expects pre-processed data.
|
||||
*/
|
||||
export const RawPrometheusContainerPure = memo(
|
||||
({
|
||||
loading,
|
||||
onCellFilterAdded,
|
||||
tableResult,
|
||||
width,
|
||||
ariaLabel,
|
||||
showRawPrometheus,
|
||||
}: RawPrometheusContainerPureProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// If resultsStyle is undefined we won't render the toggle, and the default table will be rendered
|
||||
const [resultsStyle, setResultsStyle] = useState<TableResultsStyle | undefined>(
|
||||
showRawPrometheus ? TABLE_RESULTS_STYLE.raw : undefined
|
||||
);
|
||||
|
||||
const onChangeResultsStyle = (newResultsStyle: TableResultsStyle) => {
|
||||
setResultsStyle(newResultsStyle);
|
||||
};
|
||||
|
||||
const getTableHeight = () => {
|
||||
if (!tableResult || tableResult.length === 0) {
|
||||
return 200;
|
||||
}
|
||||
|
||||
// tries to estimate table height
|
||||
return Math.max(Math.min(600, tableResult[0].length * 35) + 35);
|
||||
};
|
||||
|
||||
const renderLabel = () => {
|
||||
const ALL_GRAPH_STYLE_OPTIONS: Array<SelectableValue<TableResultsStyle>> = TABLE_RESULTS_STYLES.map((style) => ({
|
||||
value: style,
|
||||
// capital-case it and switch `_` to ` `
|
||||
label: style[0].toUpperCase() + style.slice(1).replace(/_/, ' '),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={styles.spacing}>
|
||||
<RadioButtonGroup
|
||||
onClick={() => {
|
||||
const props = {
|
||||
state: resultsStyle === TABLE_RESULTS_STYLE.table ? TABLE_RESULTS_STYLE.raw : TABLE_RESULTS_STYLE.table,
|
||||
};
|
||||
reportInteraction('grafana_explore_prometheus_instant_query_ui_toggle_clicked', props);
|
||||
}}
|
||||
size="sm"
|
||||
options={ALL_GRAPH_STYLE_OPTIONS}
|
||||
value={resultsStyle}
|
||||
onChange={onChangeResultsStyle}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const height = getTableHeight();
|
||||
const tableWidth = width - config.theme.panelPadding * 2 - PANEL_BORDER;
|
||||
|
||||
const frames = tableResult?.filter(
|
||||
(frame: DataFrame | undefined): frame is DataFrame => !!frame && frame.length !== 0
|
||||
);
|
||||
|
||||
const title = resultsStyle === TABLE_RESULTS_STYLE.raw ? 'Raw' : 'Table';
|
||||
const label = resultsStyle !== undefined ? renderLabel() : 'Table';
|
||||
|
||||
// Render table as default if resultsStyle is not set.
|
||||
const renderTable = !resultsStyle || resultsStyle === TABLE_RESULTS_STYLE.table;
|
||||
|
||||
return (
|
||||
<PanelChrome title={title} actions={label} loadingState={loading}>
|
||||
{frames?.length && (
|
||||
<>
|
||||
{renderTable && (
|
||||
<Table
|
||||
ariaLabel={ariaLabel}
|
||||
data={frames[0]}
|
||||
width={tableWidth}
|
||||
height={height}
|
||||
onCellFilterAdded={onCellFilterAdded}
|
||||
/>
|
||||
)}
|
||||
{resultsStyle === TABLE_RESULTS_STYLE.raw && <RawListContainer tableResult={frames[0]} />}
|
||||
</>
|
||||
)}
|
||||
{!frames?.length && <MetaInfoText metaItems={[{ value: '0 series returned' }]} />}
|
||||
</PanelChrome>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RawPrometheusContainerPure.displayName = 'RawPrometheusContainerPure';
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PluginExtensionExposedComponents } from '@grafana/data';
|
||||
import CentralAlertHistorySceneExposedComponent from 'app/features/alerting/unified/components/rules/central-state-history/CentralAlertHistorySceneExposedComponent';
|
||||
import { AddToDashboardFormExposedComponent } from 'app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent';
|
||||
import { PrometheusQueryResultsContainer } from 'app/features/explore/RawPrometheus/PrometheusQueryResultsContainer';
|
||||
|
||||
import { getCoreExtensionConfigurations } from '../getCoreExtensionConfigurations';
|
||||
|
||||
@@ -43,5 +44,11 @@ exposedComponentsRegistry.register({
|
||||
description: 'Add to dashboard form',
|
||||
component: AddToDashboardFormExposedComponent,
|
||||
},
|
||||
{
|
||||
id: PluginExtensionExposedComponents.PrometheusQueryResultsV1,
|
||||
title: 'Prometheus query results',
|
||||
description: 'Display Prometheus query results with Table/Raw toggle',
|
||||
component: PrometheusQueryResultsContainer,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user