Prompt DS modal when the user adds the first panel to a dashboard (#66911)

* Prompt DS modal when the user adds the first panel to a dashboard

* Rename state prop and use toggle

* Change modal size

* Avoid flickering for dashboard modal when changing number of results

* When editing panel 1, only display when new dashboard
This commit is contained in:
Ivan Ortega Alba 2023-04-20 13:44:19 +02:00 committed by GitHub
parent 4d2570ad72
commit 40c7b3126e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 101 additions and 76 deletions

View File

@ -8,6 +8,7 @@ import { usePopper } from 'react-popper';
import { DataSourceInstanceSettings, GrafanaTheme2 } from '@grafana/data';
import { DataSourceJsonData } from '@grafana/schema';
import { Button, CustomScrollbar, Icon, Input, ModalsController, Portal, useStyles2 } from '@grafana/ui';
import config from 'app/core/config';
import { DataSourceList } from './DataSourceList';
import { DataSourceLogo, DataSourceLogoPlaceHolder } from './DataSourceLogo';
@ -157,7 +158,7 @@ const PickerContent = React.forwardRef<HTMLDivElement, PickerContentProps>((prop
</div>
<div className={styles.footer}>
{onClickAddCSV && (
{onClickAddCSV && config.featureToggles.editPanelCSVDragAndDrop && (
<Button variant="secondary" size="sm" onClick={clickAddCSVCallback}>
Add csv or spreadsheet
</Button>

View File

@ -43,64 +43,62 @@ export function DataSourceModal({
closeOnBackdropClick={true}
isOpen={true}
className={styles.modal}
contentClassName={styles.modalContent}
onClickBackdrop={onDismiss}
onDismiss={onDismiss}
>
<div className={styles.modalContent}>
<div className={styles.leftColumn}>
<Input
className={styles.searchInput}
value={search}
prefix={<Icon name="search" />}
placeholder="Search data source"
onChange={(e) => setSearch(e.currentTarget.value)}
<div className={styles.leftColumn}>
<Input
className={styles.searchInput}
value={search}
prefix={<Icon name="search" />}
placeholder="Search data source"
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<CustomScrollbar>
<DataSourceList
dashboard={false}
mixed={false}
variables
filter={(ds) => ds.name.includes(search) && !ds.meta.builtIn}
onChange={onChange}
current={current}
/>
<CustomScrollbar>
<DataSourceList
dashboard={false}
mixed={false}
variables
// FIXME: Filter out the grafana data source in a hacky way
filter={(ds) => ds.name.toLowerCase().includes(search.toLowerCase()) && ds.name !== '-- Grafana --'}
onChange={onChange}
current={current}
/>
</CustomScrollbar>
</CustomScrollbar>
</div>
<div className={styles.rightColumn}>
<div className={styles.builtInDataSources}>
<DataSourceList
className={styles.builtInDataSourceList}
filter={(ds) => !!ds.meta.builtIn}
dashboard
mixed
onChange={onChange}
current={current}
/>
{enableFileUpload && (
<FileDropzone
readAs="readAsArrayBuffer"
fileListRenderer={() => undefined}
options={{
maxSize: DFImport.maxFileSize,
multiple: false,
accept: DFImport.acceptedFiles,
...fileUploadOptions,
onDrop: (...args) => {
fileUploadOptions?.onDrop?.(...args);
onDismiss();
},
}}
>
<FileDropzoneDefaultChildren />
</FileDropzone>
)}
</div>
<div className={styles.rightColumn}>
<div className={styles.builtInDataSources}>
<DataSourceList
className={styles.builtInDataSourceList}
filter={(ds) => !!ds.meta.builtIn}
dashboard
mixed
onChange={onChange}
current={current}
/>
{enableFileUpload && (
<FileDropzone
readAs="readAsArrayBuffer"
fileListRenderer={() => undefined}
options={{
maxSize: DFImport.maxFileSize,
multiple: false,
accept: DFImport.acceptedFiles,
...fileUploadOptions,
onDrop: (...args) => {
fileUploadOptions?.onDrop?.(...args);
onDismiss();
},
}}
>
<FileDropzoneDefaultChildren />
</FileDropzone>
)}
</div>
<div className={styles.dsCTAs}>
<LinkButton variant="secondary" href={`datasources/new`}>
Configure a new data source
</LinkButton>
</div>
<div className={styles.dsCTAs}>
<LinkButton variant="secondary" href={`datasources/new`}>
Configure a new data source
</LinkButton>
</div>
</div>
</Modal>
@ -112,12 +110,12 @@ function getDataSourceModalStyles(theme: GrafanaTheme2) {
modal: css`
width: 80%;
height: 80%;
max-width: 1200px;
max-height: 900px;
`,
modalContent: css`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: stretch;
height: 100%;
`,
leftColumn: css`
@ -133,7 +131,6 @@ function getDataSourceModalStyles(theme: GrafanaTheme2) {
flex-direction: column;
width: 50%;
height: 100%;
padding: ${theme.spacing(1)};
justify-items: space-evenly;
align-items: stretch;
padding-left: ${theme.spacing(1)};

View File

@ -15,13 +15,14 @@ import {
PanelData,
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { getDataSourceSrv } from '@grafana/runtime';
import { getDataSourceSrv, locationService } from '@grafana/runtime';
import { Button, CustomScrollbar, HorizontalGroup, InlineFormLabel, Modal, stylesFactory } from '@grafana/ui';
import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
import config from 'app/core/config';
import { backendSrv } from 'app/core/services/backend_srv';
import { addQuery, queryIsEmpty } from 'app/core/utils/query';
import * as DFImport from 'app/features/dataframe-import';
import { DataSourceModal } from 'app/features/datasources/components/picker/DataSourceModal';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
import { dataSource as expressionDatasource } from 'app/features/expressions/ExpressionDatasource';
import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard';
@ -51,6 +52,7 @@ interface State {
isLoadingHelp: boolean;
isPickerOpen: boolean;
isAddingMixed: boolean;
isDataSourceModalOpen: boolean;
data: PanelData;
isHelpOpen: boolean;
defaultDataSource?: DataSourceApi;
@ -69,6 +71,7 @@ export class QueryGroup extends PureComponent<Props, State> {
querySubscription: Unsubscribable | null = null;
state: State = {
isDataSourceModalOpen: false,
isLoadingHelp: false,
helpContent: null,
isPickerOpen: false,
@ -116,6 +119,11 @@ export class QueryGroup extends PureComponent<Props, State> {
dataSource: { ...options.dataSource },
savedQueryUid: options.savedQueryUid,
},
// TODO: Detect the first panel added into a new dashboard better.
// This is flaky in case the UID is generated differently
isDataSourceModalOpen:
locationService.getLocation().pathname === '/dashboard/new' &&
locationService.getSearchObject().editPanel === '1',
});
} catch (error) {
console.log('failed to load data source', error);
@ -213,24 +221,7 @@ export class QueryGroup extends PureComponent<Props, State> {
<InlineFormLabel htmlFor="data-source-picker" width={'auto'}>
Data source
</InlineFormLabel>
<div className={styles.dataSourceRowItem}>
<DataSourcePicker
onChange={this.onChangeDataSource}
current={options.dataSource}
metrics={true}
mixed={true}
dashboard={true}
variables={true}
enableFileUpload={config.featureToggles.editPanelCSVDragAndDrop}
fileUploadOptions={{
onDrop: this.onFileDrop,
maxSize: DFImport.maxFileSize,
multiple: false,
accept: DFImport.acceptedFiles,
}}
onClickAddCSV={this.onClickAddCSV}
/>
</div>
<div className={styles.dataSourceRowItem}>{this.renderDataSourcePickerWithPrompt()}</div>
{dataSource && (
<>
<div className={styles.dataSourceRowItem}>
@ -290,6 +281,42 @@ export class QueryGroup extends PureComponent<Props, State> {
);
};
renderDataSourcePickerWithPrompt = () => {
const { isDataSourceModalOpen } = this.state;
const commonProps = {
enableFileUpload: config.featureToggles.editPanelCSVDragAndDrop,
fileUploadOptions: {
onDrop: this.onFileDrop,
maxSize: DFImport.maxFileSize,
multiple: false,
accept: DFImport.acceptedFiles,
},
current: this.props.options.dataSource,
onChange: (ds: DataSourceInstanceSettings) => {
this.onChangeDataSource(ds);
this.setState({ isDataSourceModalOpen: false });
},
};
const onDismiss = () => this.setState({ isDataSourceModalOpen: false });
return (
<>
{isDataSourceModalOpen && config.featureToggles.advancedDataSourcePicker && (
<DataSourceModal {...commonProps} onDismiss={onDismiss}></DataSourceModal>
)}
<DataSourcePicker
{...commonProps}
metrics={true}
mixed={true}
dashboard={true}
variables={true}
onClickAddCSV={this.onClickAddCSV}
/>
</>
);
};
onAddMixedQuery = (datasource: any) => {
this.onAddQuery({ datasource: datasource.name });
this.setState({ isAddingMixed: false });