mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Improve empty state when no ds picker were found (#67422)
This commit is contained in:
parent
e03a8b6826
commit
1afaf4d73e
@ -1,8 +1,6 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"packages": ["packages/*"],
|
||||
"version": "10.1.0-pre"
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
|
||||
import { LinkButton, ButtonVariant } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { ROUTES as CONNECTIONS_ROUTES } from 'app/features/connections/constants';
|
||||
import { DATASOURCES_ROUTES } from 'app/features/datasources/constants';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
interface AddNewDataSourceButtonProps {
|
||||
onClick?: () => void;
|
||||
variant?: ButtonVariant;
|
||||
}
|
||||
|
||||
export function AddNewDataSourceButton({ variant, onClick }: AddNewDataSourceButtonProps) {
|
||||
const hasCreateRights = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
|
||||
const newDataSourceURL = config.featureToggles.dataConnectionsConsole
|
||||
? CONNECTIONS_ROUTES.DataSourcesNew
|
||||
: DATASOURCES_ROUTES.New;
|
||||
|
||||
return (
|
||||
<LinkButton
|
||||
variant={variant || 'primary'}
|
||||
href={newDataSourceURL}
|
||||
disabled={!hasCreateRights}
|
||||
tooltip={!hasCreateRights ? 'You do not have permission to configure new data sources' : undefined}
|
||||
onClick={onClick}
|
||||
>
|
||||
Configure a new data source
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
@ -25,6 +25,7 @@ const INTERACTION_ITEM = {
|
||||
SELECT_DS: 'select_ds',
|
||||
ADD_FILE: 'add_file',
|
||||
OPEN_ADVANCED_DS_PICKER: 'open_advanced_ds_picker',
|
||||
CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state',
|
||||
};
|
||||
|
||||
export function DataSourceDropdown(props: DataSourceDropdownProps) {
|
||||
@ -205,16 +206,19 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
|
||||
|
||||
return (
|
||||
<div style={props.style} ref={ref} className={styles.container}>
|
||||
<div className={styles.dataSourceList}>
|
||||
<DataSourceList
|
||||
{...props}
|
||||
enableKeyboardNavigation
|
||||
current={current}
|
||||
onChange={changeCallback}
|
||||
filter={(ds) => matchDataSourceWithSearch(ds, filterTerm)}
|
||||
></DataSourceList>
|
||||
</div>
|
||||
|
||||
<DataSourceList
|
||||
{...props}
|
||||
enableKeyboardNavigation
|
||||
className={styles.dataSourceList}
|
||||
current={current}
|
||||
onChange={changeCallback}
|
||||
filter={(ds) => matchDataSourceWithSearch(ds, filterTerm)}
|
||||
onClickEmptyStateCTA={() =>
|
||||
reportInteraction(INTERACTION_EVENT_NAME, {
|
||||
item: INTERACTION_ITEM.CONFIG_NEW_DS_EMPTY_STATE,
|
||||
})
|
||||
}
|
||||
></DataSourceList>
|
||||
<div className={styles.footer}>
|
||||
{onClickAddCSV && config.featureToggles.editPanelCSVDragAndDrop && (
|
||||
<Button variant="secondary" size="sm" onClick={clickAddCSVCallback}>
|
||||
|
@ -4,10 +4,11 @@ import { Observable } from 'rxjs';
|
||||
|
||||
import { DataSourceInstanceSettings, DataSourceRef, GrafanaTheme2 } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { useTheme2 } from '@grafana/ui';
|
||||
import { useStyles2, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { useDatasources, useKeyboardNavigatableList, useRecentlyUsedDataSources } from '../../hooks';
|
||||
|
||||
import { AddNewDataSourceButton } from './AddNewDataSourceButton';
|
||||
import { DataSourceCard } from './DataSourceCard';
|
||||
import { getDataSourceCompareFn, isDataSourceMatch } from './utils';
|
||||
|
||||
@ -37,6 +38,7 @@ export interface DataSourceListProps {
|
||||
inputId?: string;
|
||||
filter?: (dataSource: DataSourceInstanceSettings) => boolean;
|
||||
onClear?: () => void;
|
||||
onClickEmptyStateCTA?: () => void;
|
||||
enableKeyboardNavigation?: boolean;
|
||||
}
|
||||
|
||||
@ -51,7 +53,7 @@ export function DataSourceList(props: DataSourceListProps) {
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme, selectedItemCssSelector);
|
||||
|
||||
const { className, current, onChange, enableKeyboardNavigation } = props;
|
||||
const { className, current, onChange, enableKeyboardNavigation, onClickEmptyStateCTA } = props;
|
||||
// QUESTION: Should we use data from the Redux store as admin DS view does?
|
||||
const dataSources = useDatasources({
|
||||
alerting: props.alerting,
|
||||
@ -67,11 +69,14 @@ export function DataSourceList(props: DataSourceListProps) {
|
||||
});
|
||||
|
||||
const [recentlyUsedDataSources, pushRecentlyUsedDataSource] = useRecentlyUsedDataSources();
|
||||
const filteredDataSources = props.filter ? dataSources.filter(props.filter) : dataSources;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={cx(className, styles.container)}>
|
||||
{dataSources
|
||||
.filter((ds) => (props.filter ? props.filter(ds) : true))
|
||||
{filteredDataSources.length === 0 && (
|
||||
<EmptyState className={styles.emptyState} onClickCTA={onClickEmptyStateCTA} />
|
||||
)}
|
||||
{filteredDataSources
|
||||
.sort(getDataSourceCompareFn(current, recentlyUsedDataSources, getDataSourceVariableIDs()))
|
||||
.map((ds) => (
|
||||
<DataSourceCard
|
||||
@ -89,6 +94,30 @@ export function DataSourceList(props: DataSourceListProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function EmptyState({ className, onClickCTA }: { className?: string; onClickCTA?: () => void }) {
|
||||
const styles = useStyles2(getEmptyStateStyles);
|
||||
return (
|
||||
<div className={cx(className, styles.container)}>
|
||||
<p className={styles.message}>No data sources found</p>
|
||||
<AddNewDataSourceButton onClick={onClickCTA} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getEmptyStateStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
container: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`,
|
||||
message: css`
|
||||
margin-bottom: ${theme.spacing(3)};
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
function getDataSourceVariableIDs() {
|
||||
const templateSrv = getTemplateSrv();
|
||||
/** Unforunately there is no easy way to identify data sources that are variables. The uid of the data source will be the name of the variable in a templating syntax $([name]) **/
|
||||
@ -101,9 +130,15 @@ function getDataSourceVariableIDs() {
|
||||
function getStyles(theme: GrafanaTheme2, selectedItemCssSelector: string) {
|
||||
return {
|
||||
container: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
${selectedItemCssSelector} {
|
||||
background-color: ${theme.colors.background.secondary};
|
||||
}
|
||||
`,
|
||||
emptyState: css`
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -10,18 +10,13 @@ import {
|
||||
FileDropzone,
|
||||
FileDropzoneDefaultChildren,
|
||||
CustomScrollbar,
|
||||
LinkButton,
|
||||
useStyles2,
|
||||
Input,
|
||||
Icon,
|
||||
} from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { ROUTES as CONNECTIONS_ROUTES } from 'app/features/connections/constants';
|
||||
import * as DFImport from 'app/features/dataframe-import';
|
||||
import { DATASOURCES_ROUTES } from 'app/features/datasources/constants';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { AddNewDataSourceButton } from './AddNewDataSourceButton';
|
||||
import { DataSourceList } from './DataSourceList';
|
||||
import { matchDataSourceWithSearch } from './utils';
|
||||
|
||||
@ -30,6 +25,7 @@ const INTERACTION_ITEM = {
|
||||
SELECT_DS: 'select_ds',
|
||||
UPLOAD_FILE: 'upload_file',
|
||||
CONFIG_NEW_DS: 'config_new_ds',
|
||||
CONFIG_NEW_DS_EMPTY_STATE: 'config_new_ds_empty_state',
|
||||
SEARCH: 'search',
|
||||
DISMISS: 'dismiss',
|
||||
};
|
||||
@ -54,11 +50,7 @@ export function DataSourceModal({
|
||||
}: DataSourceModalProps) {
|
||||
const styles = useStyles2(getDataSourceModalStyles);
|
||||
const [search, setSearch] = useState('');
|
||||
const hasCreateRights = contextSrv.hasPermission(AccessControlAction.DataSourcesCreate);
|
||||
const analyticsInteractionSrc = reportedInteractionFrom || 'modal';
|
||||
const newDataSourceURL = config.featureToggles.dataConnectionsConsole
|
||||
? CONNECTIONS_ROUTES.DataSourcesNew
|
||||
: DATASOURCES_ROUTES.New;
|
||||
|
||||
const onDismissModal = () => {
|
||||
onDismiss();
|
||||
@ -106,12 +98,19 @@ export function DataSourceModal({
|
||||
/>
|
||||
<CustomScrollbar>
|
||||
<DataSourceList
|
||||
className={styles.dataSourceList}
|
||||
dashboard={false}
|
||||
mixed={false}
|
||||
variables
|
||||
filter={(ds) => matchDataSourceWithSearch(ds, search) && !ds.meta.builtIn}
|
||||
onChange={onChangeDataSource}
|
||||
current={current}
|
||||
onClickEmptyStateCTA={() =>
|
||||
reportInteraction(INTERACTION_EVENT_NAME, {
|
||||
item: INTERACTION_ITEM.CONFIG_NEW_DS_EMPTY_STATE,
|
||||
src: analyticsInteractionSrc,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
@ -149,20 +148,15 @@ export function DataSourceModal({
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.dsCTAs}>
|
||||
<LinkButton
|
||||
<AddNewDataSourceButton
|
||||
variant="secondary"
|
||||
href={newDataSourceURL}
|
||||
disabled={!hasCreateRights}
|
||||
tooltip={!hasCreateRights ? 'You do not have permission to configure new data sources' : undefined}
|
||||
onClick={() => {
|
||||
reportInteraction(INTERACTION_EVENT_NAME, {
|
||||
item: INTERACTION_ITEM.CONFIG_NEW_DS,
|
||||
src: analyticsInteractionSrc,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Configure a new data source
|
||||
</LinkButton>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@ -203,6 +197,9 @@ function getDataSourceModalStyles(theme: GrafanaTheme2) {
|
||||
flex: 1;
|
||||
margin-bottom: ${theme.spacing(4)};
|
||||
`,
|
||||
dataSourceList: css`
|
||||
height: 100%;
|
||||
`,
|
||||
builtInDataSourceList: css`
|
||||
margin-bottom: ${theme.spacing(4)};
|
||||
`,
|
||||
|
Loading…
Reference in New Issue
Block a user