mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			251 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			251 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
| import { Column, Row } from 'react-table';
 | |
| import memoizeOne from 'memoize-one';
 | |
| import { ContentPosition } from 'csstype';
 | |
| import {
 | |
|   DataFrame,
 | |
|   Field,
 | |
|   FieldType,
 | |
|   formattedValueToString,
 | |
|   getFieldDisplayName,
 | |
|   SelectableValue,
 | |
| } from '@grafana/data';
 | |
| 
 | |
| import { DefaultCell } from './DefaultCell';
 | |
| import { BarGaugeCell } from './BarGaugeCell';
 | |
| import { CellComponent, TableCellDisplayMode, TableFieldOptions, FooterItem } from './types';
 | |
| import { JSONViewCell } from './JSONViewCell';
 | |
| import { ImageCell } from './ImageCell';
 | |
| import { getFooterValue } from './FooterRow';
 | |
| 
 | |
| export function getTextAlign(field?: Field): ContentPosition {
 | |
|   if (!field) {
 | |
|     return 'flex-start';
 | |
|   }
 | |
| 
 | |
|   if (field.config.custom) {
 | |
|     const custom = field.config.custom as TableFieldOptions;
 | |
| 
 | |
|     switch (custom.align) {
 | |
|       case 'right':
 | |
|         return 'flex-end';
 | |
|       case 'left':
 | |
|         return 'flex-start';
 | |
|       case 'center':
 | |
|         return 'center';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (field.type === FieldType.number) {
 | |
|     return 'flex-end';
 | |
|   }
 | |
| 
 | |
|   return 'flex-start';
 | |
| }
 | |
| 
 | |
| export function getColumns(
 | |
|   data: DataFrame,
 | |
|   availableWidth: number,
 | |
|   columnMinWidth: number,
 | |
|   footerValues?: FooterItem[]
 | |
| ): Column[] {
 | |
|   const columns: any[] = [];
 | |
|   let fieldCountWithoutWidth = data.fields.length;
 | |
| 
 | |
|   for (const [fieldIndex, field] of data.fields.entries()) {
 | |
|     const fieldTableOptions = (field.config.custom || {}) as TableFieldOptions;
 | |
| 
 | |
|     if (fieldTableOptions.hidden) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (fieldTableOptions.width) {
 | |
|       availableWidth -= fieldTableOptions.width;
 | |
|       fieldCountWithoutWidth -= 1;
 | |
|     }
 | |
| 
 | |
|     const selectSortType = (type: FieldType): string => {
 | |
|       switch (type) {
 | |
|         case FieldType.number:
 | |
|           return 'number';
 | |
|         case FieldType.time:
 | |
|           return 'basic';
 | |
|         default:
 | |
|           return 'alphanumeric-insensitive';
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     const Cell = getCellComponent(fieldTableOptions.displayMode, field);
 | |
|     columns.push({
 | |
|       Cell,
 | |
|       id: fieldIndex.toString(),
 | |
|       Header: getFieldDisplayName(field, data),
 | |
|       accessor: (row: any, i: number) => {
 | |
|         return field.values.get(i);
 | |
|       },
 | |
|       sortType: selectSortType(field.type),
 | |
|       width: fieldTableOptions.width,
 | |
|       minWidth: fieldTableOptions.minWidth || columnMinWidth,
 | |
|       filter: memoizeOne(filterByValue(field)),
 | |
|       justifyContent: getTextAlign(field),
 | |
|       Footer: getFooterValue(fieldIndex, footerValues),
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   // set columns that are at minimum width
 | |
|   let sharedWidth = availableWidth / fieldCountWithoutWidth;
 | |
|   for (let i = fieldCountWithoutWidth; i > 0; i--) {
 | |
|     for (const column of columns) {
 | |
|       if (!column.width && column.minWidth > sharedWidth) {
 | |
|         column.width = column.minWidth;
 | |
|         availableWidth -= column.width;
 | |
|         fieldCountWithoutWidth -= 1;
 | |
|         sharedWidth = availableWidth / fieldCountWithoutWidth;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // divide up the rest of the space
 | |
|   for (const column of columns) {
 | |
|     if (!column.width) {
 | |
|       column.width = sharedWidth;
 | |
|     }
 | |
|     column.minWidth = 50;
 | |
|   }
 | |
| 
 | |
|   return columns;
 | |
| }
 | |
| 
 | |
| function getCellComponent(displayMode: TableCellDisplayMode, field: Field): CellComponent {
 | |
|   switch (displayMode) {
 | |
|     case TableCellDisplayMode.ColorText:
 | |
|     case TableCellDisplayMode.ColorBackground:
 | |
|       return DefaultCell;
 | |
|     case TableCellDisplayMode.Image:
 | |
|       return ImageCell;
 | |
|     case TableCellDisplayMode.LcdGauge:
 | |
|     case TableCellDisplayMode.BasicGauge:
 | |
|     case TableCellDisplayMode.GradientGauge:
 | |
|       return BarGaugeCell;
 | |
|     case TableCellDisplayMode.JSONView:
 | |
|       return JSONViewCell;
 | |
|   }
 | |
| 
 | |
|   // Default or Auto
 | |
|   if (field.type === FieldType.other) {
 | |
|     return JSONViewCell;
 | |
|   }
 | |
|   return DefaultCell;
 | |
| }
 | |
| 
 | |
| export function filterByValue(field?: Field) {
 | |
|   return function (rows: Row[], id: string, filterValues?: SelectableValue[]) {
 | |
|     if (rows.length === 0) {
 | |
|       return rows;
 | |
|     }
 | |
| 
 | |
|     if (!filterValues) {
 | |
|       return rows;
 | |
|     }
 | |
| 
 | |
|     if (!field) {
 | |
|       return rows;
 | |
|     }
 | |
| 
 | |
|     return rows.filter((row) => {
 | |
|       if (!row.values.hasOwnProperty(id)) {
 | |
|         return false;
 | |
|       }
 | |
|       const value = rowToFieldValue(row, field);
 | |
|       return filterValues.find((filter) => filter.value === value) !== undefined;
 | |
|     });
 | |
|   };
 | |
| }
 | |
| 
 | |
| export function calculateUniqueFieldValues(rows: any[], field?: Field) {
 | |
|   if (!field || rows.length === 0) {
 | |
|     return {};
 | |
|   }
 | |
| 
 | |
|   const set: Record<string, any> = {};
 | |
| 
 | |
|   for (let index = 0; index < rows.length; index++) {
 | |
|     const value = rowToFieldValue(rows[index], field);
 | |
|     set[value || '(Blanks)'] = value;
 | |
|   }
 | |
| 
 | |
|   return set;
 | |
| }
 | |
| 
 | |
| export function rowToFieldValue(row: any, field?: Field): string {
 | |
|   if (!field || !row) {
 | |
|     return '';
 | |
|   }
 | |
| 
 | |
|   const fieldValue = field.values.get(row.index);
 | |
|   const displayValue = field.display ? field.display(fieldValue) : fieldValue;
 | |
|   const value = field.display ? formattedValueToString(displayValue) : displayValue;
 | |
| 
 | |
|   return value;
 | |
| }
 | |
| 
 | |
| export function valuesToOptions(unique: Record<string, any>): SelectableValue[] {
 | |
|   return Object.keys(unique)
 | |
|     .reduce((all, key) => all.concat({ value: unique[key], label: key }), [] as SelectableValue[])
 | |
|     .sort(sortOptions);
 | |
| }
 | |
| 
 | |
| export function sortOptions(a: SelectableValue, b: SelectableValue): number {
 | |
|   if (a.label === undefined && b.label === undefined) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   if (a.label === undefined && b.label !== undefined) {
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   if (a.label !== undefined && b.label === undefined) {
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   if (a.label! < b.label!) {
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   if (a.label! > b.label!) {
 | |
|     return 1;
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| export function getFilteredOptions(options: SelectableValue[], filterValues?: SelectableValue[]): SelectableValue[] {
 | |
|   if (!filterValues) {
 | |
|     return [];
 | |
|   }
 | |
| 
 | |
|   return options.filter((option) => filterValues.some((filtered) => filtered.value === option.value));
 | |
| }
 | |
| 
 | |
| export function sortCaseInsensitive(a: Row<any>, b: Row<any>, id: string) {
 | |
|   return String(a.values[id]).localeCompare(String(b.values[id]), undefined, { sensitivity: 'base' });
 | |
| }
 | |
| 
 | |
| // sortNumber needs to have great performance as it is called a lot
 | |
| export function sortNumber(rowA: Row<any>, rowB: Row<any>, id: string) {
 | |
|   const a = toNumber(rowA.values[id]);
 | |
|   const b = toNumber(rowB.values[id]);
 | |
|   return a === b ? 0 : a > b ? 1 : -1;
 | |
| }
 | |
| 
 | |
| function toNumber(value: any): number {
 | |
|   if (typeof value === 'number') {
 | |
|     return value;
 | |
|   }
 | |
| 
 | |
|   if (value === null || value === undefined || value === '' || isNaN(value)) {
 | |
|     return Number.NEGATIVE_INFINITY;
 | |
|   }
 | |
| 
 | |
|   return Number(value);
 | |
| }
 |