Improve empty state when no ds picker were found (#67422)

pull/67830/head
Ivan Ortega Alba 2 years ago committed by GitHub
parent e03a8b6826
commit 1afaf4d73e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      lerna.json
  2. 32
      public/app/features/datasources/components/picker/AddNewDataSourceButton.tsx
  3. 24
      public/app/features/datasources/components/picker/DataSourceDropdown.tsx
  4. 43
      public/app/features/datasources/components/picker/DataSourceList.tsx
  5. 31
      public/app/features/datasources/components/picker/DataSourceModal.tsx

@ -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…
Cancel
Save