import { css } from '@emotion/css'; import { negate } from 'lodash'; import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { CellProps, SortByFn } from 'react-table'; import { GrafanaTheme2 } from '@grafana/data'; import { Badge, Button, DeleteButton, HorizontalGroup, LoadingPlaceholder, useStyles2, Alert } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { contextSrv } from 'app/core/core'; import { useNavModel } from 'app/core/hooks/useNavModel'; import { AccessControlAction } from 'app/types'; import { AddCorrelationForm } from './Forms/AddCorrelationForm'; import { EditCorrelationForm } from './Forms/EditCorrelationForm'; import { EmptyCorrelationsCTA } from './components/EmptyCorrelationsCTA'; import { Column, Table } from './components/Table'; import { RemoveCorrelationParams } from './types'; import { CorrelationData, useCorrelations } from './useCorrelations'; const sortDatasource: SortByFn = (a, b, column) => a.values[column].name.localeCompare(b.values[column].name); const isSourceReadOnly = ({ source }: Pick) => source.readOnly; const loaderWrapper = css` display: flex; justify-content: center; `; export default function CorrelationsPage() { const navModel = useNavModel('correlations'); const [isAdding, setIsAdding] = useState(false); const { remove, get } = useCorrelations(); useEffect(() => { get.execute(); // we only want to fetch data on first render // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const canWriteCorrelations = contextSrv.hasPermission(AccessControlAction.DataSourcesWrite); const handleAdd = useCallback(() => { get.execute(); setIsAdding(false); }, [get]); const handleUpdate = useCallback(() => { get.execute(); }, [get]); const handleRemove = useCallback<(params: RemoveCorrelationParams) => void>( async (correlation) => { await remove.execute(correlation); get.execute(); }, [remove, get] ); const RowActions = useCallback( ({ row: { original: { source: { uid: sourceUID, readOnly }, uid, }, }, }: CellProps) => !readOnly && ( handleRemove({ sourceUID, uid })} closeOnConfirm /> ), [handleRemove] ); const columns = useMemo>>( () => [ { cell: InfoCell, shrink: true, visible: (data) => data.some(isSourceReadOnly), }, { id: 'source', header: 'Source', cell: DataSourceCell, sortType: sortDatasource, }, { id: 'target', header: 'Target', cell: DataSourceCell, sortType: sortDatasource, }, { id: 'label', header: 'Label', sortType: 'alphanumeric' }, { cell: RowActions, shrink: true, visible: (data) => canWriteCorrelations && data.some(negate(isSourceReadOnly)), }, ], [RowActions, canWriteCorrelations] ); const data = useMemo(() => get.value, [get.value]); const showEmptyListCTA = data?.length === 0 && !isAdding && (!get.error || get.error.status === 404); return (

Correlations

Define how data living in different data sources relates to each other.

{canWriteCorrelations && data?.length !== 0 && data !== undefined && !isAdding && ( )}
{!data && get.loading && (
)} {showEmptyListCTA && setIsAdding(true)} />} { // This error is not actionable, it'd be nice to have a recovery button get.error && get.error.status !== 404 && ( {get.error.data.message || 'An unknown error occurred while fetching correlation data. Please try again.'} ) } {isAdding && setIsAdding(false)} onCreated={handleAdd} />} {data && data.length >= 1 && ( ( )} columns={columns} data={data} getRowId={(correlation) => `${correlation.source.uid}-${correlation.uid}`} /> )} ); } const getDatasourceCellStyles = (theme: GrafanaTheme2) => ({ root: css` display: flex; align-items: center; `, dsLogo: css` margin-right: ${theme.spacing()}; height: 16px; width: 16px; `, }); const DataSourceCell = memo( function DataSourceCell({ cell: { value }, }: CellProps) { const styles = useStyles2(getDatasourceCellStyles); return ( {value.name} ); }, ({ cell: { value } }, { cell: { value: prevValue } }) => { return value.type === prevValue.type && value.name === prevValue.name; } ); const noWrap = css` white-space: nowrap; `; const InfoCell = memo( function InfoCell({ ...props }: CellProps) { const readOnly = props.row.original.source.readOnly; if (readOnly) { return ; } else { return null; } }, (props, prevProps) => props.row.original.source.readOnly === prevProps.row.original.source.readOnly );