/usr/share/grafana/public/app/features/explore/PrometheusListView
import { css } from '@emotion/css'; import { cloneDeep } from 'lodash'; import { useEffect, useId, useRef, useState } from 'react'; import { useWindowSize } from 'react-use'; import { VariableSizeList as List } from 'react-window'; import { DataFrame, Field as DataFrameField } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; import { Field, Switch } from '@grafana/ui'; import { ItemLabels } from './ItemLabels'; import RawListItem from './RawListItem'; import { getRawPrometheusListItemsFromDataFrame, RawPrometheusListItemEmptyValue, } from './utils/getRawPrometheusListItemsFromDataFrame'; export type instantQueryRawVirtualizedListData = { Value: string; __name__?: string; [index: string]: string | undefined; }; export interface RawListContainerProps { tableResult: DataFrame; } const styles = { wrapper: css({ height: '100%', overflow: 'scroll', }), switchWrapper: css({ display: 'flex', flexDirection: 'row', marginBottom: 0, }), switchLabel: css({ marginLeft: '15px', marginBottom: 0, }), switch: css({ marginLeft: '10px', }), resultCount: css({ marginBottom: '4px', }), header: css({ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', fontSize: '12px', lineHeight: 1.25, }), }; const mobileWidthThreshold = 480; const numberOfColumnsBeforeExpandedViewIsDefault = 2; /** * The container that provides the virtualized list to the child components * @param props * @constructor */ const RawListContainer = (props: RawListContainerProps) => { const { tableResult } = props; const dataFrame = cloneDeep(tableResult); const listRef = useRef<List | null>(null); const valueLabels = dataFrame.fields.filter((field) => field.name.includes('Value')); const items = getRawPrometheusListItemsFromDataFrame(dataFrame); const { width } = useWindowSize(); const [isExpandedView, setIsExpandedView] = useState( width <= mobileWidthThreshold || valueLabels.length > numberOfColumnsBeforeExpandedViewIsDefault ); const onContentClick = () => { setIsExpandedView(!isExpandedView); const props = { isExpanded: !isExpandedView, }; reportInteraction('grafana_explore_prometheus_instant_query_ui_raw_toggle_expand', props); }; useEffect(() => { // After the expanded view has updated, tell the list to re-render listRef.current?.resetAfterIndex(0, true); }, [isExpandedView]); const calculateInitialHeight = (length: number): number => { const maxListHeight = 600; const shortListLength = 10; if (length < shortListLength) { let sum = 0; for (let i = 0; i < length; i++) { sum += getListItemHeight(i, true); } return Math.min(maxListHeight, sum); } return maxListHeight; }; const getListItemHeight = (itemIndex: number, isExpandedView: boolean) => { const singleLineHeight = 32; const additionalLineHeight = 22; if (!isExpandedView) { return singleLineHeight; } const item = items[itemIndex]; // Height of 1.5 lines, plus the number of non-value attributes times the height of additional lines return 1.5 * singleLineHeight + (Object.keys(item).length - valueLabels.length) * additionalLineHeight; }; const switchId = `isExpandedView ${useId()}`; return ( <section> <header className={styles.header}> <Field className={styles.switchWrapper} label={t('explore.raw-list-container.label-expand-results', 'Expand results')} htmlFor={'isExpandedView'} noMargin > <div className={styles.switch}> <Switch onChange={onContentClick} id={switchId} value={isExpandedView} label={t('explore.raw-list-container.label-expand-results', 'Expand results')} /> </div> </Field> <div className={styles.resultCount}> <Trans i18nKey="explore.raw-list-container.item-count" values={{ numItems: items.length }}> Result series: {'{{numItems}}'} </Trans> </div> </header> <div role={'table'}> { <> {/* Show the value headings above all the values, but only if we're in the contracted view */} {valueLabels.length > 1 && !isExpandedView && ( <ItemLabels valueLabels={valueLabels} expanded={isExpandedView} /> )} <List ref={listRef} itemCount={items.length} className={styles.wrapper} itemSize={(index) => getListItemHeight(index, isExpandedView)} height={calculateInitialHeight(items.length)} width="100%" > {({ index, style }) => { let filteredValueLabels: DataFrameField[] | undefined; if (isExpandedView) { filteredValueLabels = valueLabels.filter((valueLabel) => { const itemWithValue = items[index][valueLabel.name]; return itemWithValue && itemWithValue !== RawPrometheusListItemEmptyValue; }); } return ( <div role="row" style={{ ...style, overflow: 'hidden' }}> <RawListItem isExpandedView={isExpandedView} valueLabels={filteredValueLabels} totalNumberOfValues={valueLabels.length} listKey={items[index].__name__ || `item-${index}`} listItemData={items[index]} /> </div> ); }} </List> </> } </div> </section> ); }; export default RawListContainer;
.
Edit
..
Edit
ItemLabels.tsx
Edit
ItemValues.test.tsx
Edit
ItemValues.tsx
Edit
RawListContainer.test.tsx
Edit
RawListContainer.tsx
Edit
RawListItem.test.tsx
Edit
RawListItem.tsx
Edit
RawListItemAttributes.test.tsx
Edit
RawListItemAttributes.tsx
Edit
utils
Edit