123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- import { cx } from '@emotion/css';
- import { isNumber } from 'lodash';
- import React from 'react';
- import SVG from 'react-inlinesvg';
- import { Field, getFieldDisplayName } from '@grafana/data';
- import { config, getDataSourceSrv } from '@grafana/runtime';
- import { Checkbox, Icon, IconButton, IconName, TagList } from '@grafana/ui';
- import { PluginIconName } from 'app/features/plugins/admin/types';
- import { QueryResponse, SearchResultMeta } from '../../service';
- import { SelectionChecker, SelectionToggle } from '../selection';
- import { TableColumn } from './SearchResultsTable';
- const TYPE_COLUMN_WIDTH = 250;
- const DATASOURCE_COLUMN_WIDTH = 200;
- const SORT_FIELD_WIDTH = 175;
- export const generateColumns = (
- response: QueryResponse,
- availableWidth: number,
- selection: SelectionChecker | undefined,
- selectionToggle: SelectionToggle | undefined,
- clearSelection: () => void,
- styles: { [key: string]: string },
- onTagSelected: (tag: string) => void,
- onDatasourceChange?: (datasource?: string) => void
- ): TableColumn[] => {
- const columns: TableColumn[] = [];
- const access = response.view.fields;
- const uidField = access.uid;
- const kindField = access.kind;
- const sortField = (access as any)[response.view.dataFrame.meta?.custom?.sortBy] as Field;
- if (sortField) {
- availableWidth -= SORT_FIELD_WIDTH; // pre-allocate the space for the last column
- }
- let width = 50;
- if (selection && selectionToggle) {
- width = 30;
- columns.push({
- id: `column-checkbox`,
- width,
- Header: () => {
- if (selection('*', '*')) {
- return (
- <div className={styles.checkboxHeader}>
- <IconButton name={'check-square' as any} onClick={clearSelection} />
- </div>
- );
- }
- return (
- <div className={styles.checkboxHeader}>
- <Checkbox
- checked={false}
- onChange={(e) => {
- e.stopPropagation();
- e.preventDefault();
- const { view } = response;
- const count = Math.min(view.length, 50);
- for (let i = 0; i < count; i++) {
- const item = view.get(i);
- if (item.uid && item.kind) {
- if (!selection(item.kind, item.uid)) {
- selectionToggle(item.kind, item.uid);
- }
- }
- }
- }}
- />
- </div>
- );
- },
- Cell: (p) => {
- const uid = uidField.values.get(p.row.index);
- const kind = kindField ? kindField.values.get(p.row.index) : 'dashboard'; // HACK for now
- const selected = selection(kind, uid);
- const hasUID = uid != null; // Panels don't have UID! Likely should not be shown on pages with manage options
- return (
- <div {...p.cellProps}>
- <div className={styles.checkbox}>
- <Checkbox
- disabled={!hasUID}
- value={selected && hasUID}
- onChange={(e) => {
- selectionToggle(kind, uid);
- }}
- />
- </div>
- </div>
- );
- },
- field: uidField,
- });
- availableWidth -= width;
- }
- // Name column
- width = Math.max(availableWidth * 0.2, 300);
- columns.push({
- Cell: (p) => {
- let classNames = cx(styles.nameCellStyle);
- let name = access.name.values.get(p.row.index);
- if (!name?.length) {
- name = 'Missing title'; // normal for panels
- classNames += ' ' + styles.missingTitleText;
- }
- return (
- <a {...p.cellProps} href={p.userProps.href} className={classNames} title={name}>
- {name}
- </a>
- );
- },
- id: `column-name`,
- field: access.name!,
- Header: () => {
- return <div className={styles.headerNameStyle}>Name</div>;
- },
- width,
- });
- availableWidth -= width;
- width = TYPE_COLUMN_WIDTH;
- columns.push(makeTypeColumn(access.kind, access.panel_type, width, styles));
- availableWidth -= width;
- const meta = response.view.dataFrame.meta?.custom as SearchResultMeta;
- if (meta?.locationInfo && availableWidth > 0) {
- width = Math.max(availableWidth / 1.75, 300);
- availableWidth -= width;
- columns.push({
- Cell: (p) => {
- const parts = (access.location?.values.get(p.row.index) ?? '').split('/');
- return (
- <div {...p.cellProps} className={cx(styles.locationCellStyle)}>
- {parts.map((p) => {
- const info = meta.locationInfo[p];
- return info ? (
- <a key={p} href={info.url} className={styles.locationItem}>
- <Icon name={getIconForKind(info.kind)} /> {info.name}
- </a>
- ) : (
- <span key={p}>{p}</span>
- );
- })}
- </div>
- );
- },
- id: `column-location`,
- field: access.location ?? access.url,
- Header: 'Location',
- width,
- });
- }
- // Show datasources if we have any
- if (access.ds_uid && onDatasourceChange) {
- width = DATASOURCE_COLUMN_WIDTH;
- columns.push(
- makeDataSourceColumn(
- access.ds_uid,
- width,
- styles.typeIcon,
- styles.datasourceItem,
- styles.invalidDatasourceItem,
- onDatasourceChange
- )
- );
- availableWidth -= width;
- }
- if (availableWidth > 0) {
- columns.push(makeTagsColumn(access.tags, availableWidth, styles.tagList, onTagSelected));
- }
- if (sortField) {
- columns.push({
- Header: () => <div className={styles.sortedHeader}>{getFieldDisplayName(sortField)}</div>,
- Cell: (p) => {
- let value = sortField.values.get(p.row.index);
- try {
- if (isNumber(value)) {
- value = Number(value).toLocaleString();
- }
- } catch {}
- return (
- <div {...p.cellProps} className={styles.sortedItems}>
- {`${value}`}
- </div>
- );
- },
- id: `column-sort-field`,
- field: sortField,
- width: SORT_FIELD_WIDTH,
- });
- }
- return columns;
- };
- function getIconForKind(v: string): IconName {
- if (v === 'dashboard') {
- return 'apps';
- }
- if (v === 'folder') {
- return 'folder';
- }
- return 'question-circle';
- }
- function makeDataSourceColumn(
- field: Field<string[]>,
- width: number,
- iconClass: string,
- datasourceItemClass: string,
- invalidDatasourceItemClass: string,
- onDatasourceChange: (datasource?: string) => void
- ): TableColumn {
- const srv = getDataSourceSrv();
- return {
- id: `column-datasource`,
- field,
- Header: 'Data source',
- Cell: (p) => {
- const dslist = field.values.get(p.row.index);
- if (!dslist?.length) {
- return null;
- }
- return (
- <div {...p.cellProps} className={cx(datasourceItemClass)}>
- {dslist.map((v, i) => {
- const settings = srv.getInstanceSettings(v);
- const icon = settings?.meta?.info?.logos?.small;
- if (icon) {
- return (
- <span
- key={i}
- onClick={(e) => {
- e.stopPropagation();
- e.preventDefault();
- onDatasourceChange(settings.uid);
- }}
- >
- <img src={icon} width={14} height={14} title={settings.type} className={iconClass} />
- {settings.name}
- </span>
- );
- }
- return (
- <span className={invalidDatasourceItemClass} key={i}>
- {v}
- </span>
- );
- })}
- </div>
- );
- },
- width,
- };
- }
- function makeTypeColumn(
- kindField: Field<string>,
- typeField: Field<string>,
- width: number,
- styles: Record<string, string>
- ): TableColumn {
- return {
- id: `column-type`,
- field: kindField ?? typeField,
- Header: 'Type',
- Cell: (p) => {
- const i = p.row.index;
- const kind = kindField?.values.get(i) ?? 'dashboard';
- let icon = 'public/img/icons/unicons/apps.svg';
- let txt = 'Dashboard';
- if (kind) {
- txt = kind;
- switch (txt) {
- case 'dashboard':
- txt = 'Dashboard';
- break;
- case 'folder':
- icon = 'public/img/icons/unicons/folder.svg';
- txt = 'Folder';
- break;
- case 'panel':
- icon = `public/img/icons/unicons/${PluginIconName.panel}.svg`;
- const type = typeField.values.get(i);
- if (type) {
- txt = type;
- const info = config.panels[txt];
- if (info?.name) {
- txt = info.name;
- } else {
- switch (type) {
- case 'row':
- txt = 'Row';
- icon = `public/img/icons/unicons/bars.svg`;
- break;
- case 'singlestat': // auto-migration
- txt = 'Singlestat';
- break;
- default:
- icon = `public/img/icons/unicons/question.svg`; // plugin not found
- }
- }
- }
- break;
- }
- }
- return (
- <div {...p.cellProps} className={styles.typeText}>
- <SVG src={icon} width={14} height={14} title={txt} className={styles.typeIcon} />
- {txt}
- </div>
- );
- },
- width,
- };
- }
- function makeTagsColumn(
- field: Field<string[]>,
- width: number,
- tagListClass: string,
- onTagSelected: (tag: string) => void
- ): TableColumn {
- return {
- Cell: (p) => {
- const tags = field.values.get(p.row.index);
- return tags ? (
- <div {...p.cellProps}>
- <TagList className={tagListClass} tags={tags} onClick={onTagSelected} />
- </div>
- ) : null;
- },
- id: `column-tags`,
- field: field,
- Header: 'Tags',
- width,
- };
- }
|