grafana/public/app/features/connections/tabs/ConnectData/ConnectData.tsx
Levente Balogh 4ef82dc73f
Connections: Show a "No access" modal if the user has no permissions (#61397)
* feat: add a new modal for displaying no-access info

* feat(CardGrid): add an onClick handler for items

* feat: open a no-access modal when clicking on a connection in the catlog

* feat: update permissions

Open a "No access" modal when the user clicks a connection type but has no permissions creating a datasource out of it

* test: add tests for opening the No Access modal

* test: fix the user permissions in tests

* Wip

* Revert "Wip"

This reverts commit 7f080c7f77.
2023-01-18 15:34:23 +01:00

98 lines
2.9 KiB
TypeScript

import { css } from '@emotion/css';
import React, { useMemo, useState } from 'react';
import { PluginType } from '@grafana/data';
import { useStyles2, LoadingPlaceholder } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { useGetAllWithFilters } from 'app/features/plugins/admin/state/hooks';
import { AccessControlAction } from 'app/types';
import { ROUTES } from '../../constants';
import { CardGrid, type CardGridItem } from './CardGrid';
import { CategoryHeader } from './CategoryHeader';
import { NoAccessModal } from './NoAccessModal';
import { NoResults } from './NoResults';
import { Search } from './Search';
const getStyles = () => ({
spacer: css`
height: 16px;
`,
modal: css`
width: 500px;
`,
modalContent: css`
overflow: visible;
`,
});
export function ConnectData() {
const [searchTerm, setSearchTerm] = useState('');
const [isNoAccessModalOpen, setIsNoAccessModalOpen] = useState(false);
const [focusedItem, setFocusedItem] = useState<CardGridItem | null>(null);
const styles = useStyles2(getStyles);
const canCreateDataSources = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
const handleSearchChange = (e: React.FormEvent<HTMLInputElement>) => {
setSearchTerm(e.currentTarget.value.toLowerCase());
};
const { isLoading, error, plugins } = useGetAllWithFilters({
query: searchTerm,
filterBy: '',
filterByType: PluginType.datasource,
});
const cardGridItems = useMemo(
() =>
plugins.map((plugin) => ({
id: plugin.id,
name: plugin.name,
description: plugin.description,
logo: plugin.info.logos.small,
url: ROUTES.DataSourcesDetails.replace(':id', plugin.id),
})),
[plugins]
);
const onClickCardGridItem = (e: React.MouseEvent<HTMLElement>, item: CardGridItem) => {
if (!canCreateDataSources) {
e.preventDefault();
e.stopPropagation();
openModal(item);
}
};
const openModal = (item: CardGridItem) => {
setIsNoAccessModalOpen(true);
setFocusedItem(item);
};
const closeModal = () => {
setIsNoAccessModalOpen(false);
setFocusedItem(null);
};
const showNoResults = useMemo(() => !isLoading && !error && plugins.length < 1, [isLoading, error, plugins]);
return (
<>
{focusedItem && <NoAccessModal item={focusedItem} isOpen={isNoAccessModalOpen} onDismiss={closeModal} />}
<Search onChange={handleSearchChange} />
{/* We need this extra spacing when there are no filters */}
<div className={styles.spacer} />
<CategoryHeader iconName="database" label="Data sources" />
{isLoading ? (
<LoadingPlaceholder text="Loading..." />
) : !!error ? (
<p>Error: {error.message}</p>
) : (
<CardGrid items={cardGridItems} onClickItem={onClickCardGridItem} />
)}
{showNoResults && <NoResults />}
</>
);
}