mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: moving the DataSourcePicker to grafana/runtime so it can be reused in plugins (#31628)
* moved the datasource picker to grafana-runtime. * fixed imports. * added e2e selectors as an external package. * adding react as external package. * exposing dependent types for DataSourcePicker. * added docs for ui components. * moving component to components.
This commit is contained in:
@@ -1,151 +0,0 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { HorizontalGroup, Select } from '@grafana/ui';
|
||||
import { DataSourceInstanceSettings, SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { isUnsignedPluginSignature, PluginSignatureBadge } from '../../../features/plugins/PluginSignatureBadge';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
|
||||
export interface Props {
|
||||
onChange: (ds: DataSourceInstanceSettings) => void;
|
||||
current: string | null;
|
||||
hideTextValue?: boolean;
|
||||
onBlur?: () => void;
|
||||
autoFocus?: boolean;
|
||||
openMenuOnFocus?: boolean;
|
||||
placeholder?: string;
|
||||
tracing?: boolean;
|
||||
mixed?: boolean;
|
||||
dashboard?: boolean;
|
||||
metrics?: boolean;
|
||||
annotations?: boolean;
|
||||
variables?: boolean;
|
||||
pluginId?: string;
|
||||
noDefault?: boolean;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export class DataSourcePicker extends PureComponent<Props, State> {
|
||||
dataSourceSrv = getDataSourceSrv();
|
||||
|
||||
static defaultProps: Partial<Props> = {
|
||||
autoFocus: false,
|
||||
openMenuOnFocus: false,
|
||||
placeholder: 'Select datasource',
|
||||
};
|
||||
|
||||
state: State = {};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { current } = this.props;
|
||||
const dsSettings = this.dataSourceSrv.getInstanceSettings(current);
|
||||
if (!dsSettings) {
|
||||
this.setState({ error: 'Could not find data source ' + current });
|
||||
}
|
||||
}
|
||||
|
||||
onChange = (item: SelectableValue<string>) => {
|
||||
const dsSettings = this.dataSourceSrv.getInstanceSettings(item.value);
|
||||
|
||||
if (dsSettings) {
|
||||
this.props.onChange(dsSettings);
|
||||
this.setState({ error: undefined });
|
||||
}
|
||||
};
|
||||
|
||||
private getCurrentValue() {
|
||||
const { current, hideTextValue, noDefault } = this.props;
|
||||
|
||||
if (!current && noDefault) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ds = this.dataSourceSrv.getInstanceSettings(current);
|
||||
|
||||
if (ds) {
|
||||
return {
|
||||
label: ds.name.substr(0, 37),
|
||||
value: ds.name,
|
||||
imgUrl: ds.meta.info.logos.small,
|
||||
hideText: hideTextValue,
|
||||
meta: ds.meta,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: (current ?? 'no name') + ' - not found',
|
||||
value: current,
|
||||
imgUrl: '',
|
||||
hideText: hideTextValue,
|
||||
};
|
||||
}
|
||||
|
||||
getDataSourceOptions() {
|
||||
const { tracing, metrics, mixed, dashboard, variables, annotations, pluginId } = this.props;
|
||||
const options = this.dataSourceSrv
|
||||
.getList({
|
||||
tracing,
|
||||
metrics,
|
||||
dashboard,
|
||||
mixed,
|
||||
variables,
|
||||
annotations,
|
||||
pluginId,
|
||||
})
|
||||
.map((ds) => ({
|
||||
value: ds.name,
|
||||
label: `${ds.name}${ds.isDefault ? ' (default)' : ''}`,
|
||||
imgUrl: ds.meta.info.logos.small,
|
||||
meta: ds.meta,
|
||||
}));
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { autoFocus, onBlur, openMenuOnFocus, placeholder } = this.props;
|
||||
const { error } = this.state;
|
||||
const options = this.getDataSourceOptions();
|
||||
const value = this.getCurrentValue();
|
||||
|
||||
return (
|
||||
<div aria-label={selectors.components.DataSourcePicker.container}>
|
||||
<Select
|
||||
className="ds-picker select-container"
|
||||
isMulti={false}
|
||||
isClearable={false}
|
||||
backspaceRemovesValue={false}
|
||||
onChange={this.onChange}
|
||||
options={options}
|
||||
autoFocus={autoFocus}
|
||||
onBlur={onBlur}
|
||||
openMenuOnFocus={openMenuOnFocus}
|
||||
maxMenuHeight={500}
|
||||
placeholder={placeholder}
|
||||
noOptionsMessage="No datasources found"
|
||||
value={value}
|
||||
invalid={!!error}
|
||||
getOptionLabel={(o) => {
|
||||
if (o.meta && isUnsignedPluginSignature(o.meta.signature) && o !== value) {
|
||||
return (
|
||||
<HorizontalGroup align="center" justify="space-between">
|
||||
<span>{o.label}</span> <PluginSignatureBadge status={o.meta.signature} />
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
return o.label || '';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
GrafanaTheme,
|
||||
updateDatasourcePluginJsonDataOption,
|
||||
} from '@grafana/data';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { InlineFormLabel, TagsInput, useStyles } from '@grafana/ui';
|
||||
import { css } from 'emotion';
|
||||
import React from 'react';
|
||||
import { DataSourcePicker } from './Select/DataSourcePicker';
|
||||
|
||||
export interface TraceToLogsOptions {
|
||||
datasourceUid?: string;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import { GrafanaTheme, PanelPluginMeta, PluginState } from '@grafana/data';
|
||||
import { Badge, BadgeProps, styleMixins, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { GrafanaTheme, isUnsignedPluginSignature, PanelPluginMeta, PluginState } from '@grafana/data';
|
||||
import { Badge, BadgeProps, PluginSignatureBadge, styleMixins, stylesFactory, useTheme } from '@grafana/ui';
|
||||
import { css, cx } from 'emotion';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { isUnsignedPluginSignature, PluginSignatureBadge } from '../../../plugins/PluginSignatureBadge';
|
||||
|
||||
interface Props {
|
||||
isCurrent: boolean;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { FC, PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { DataSourcePluginMeta, NavModel } from '@grafana/data';
|
||||
import { Button, LinkButton, List } from '@grafana/ui';
|
||||
import { Button, LinkButton, List, PluginSignatureBadge } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
@@ -11,7 +11,6 @@ import { addDataSource, loadDataSourcePlugins } from './state/actions';
|
||||
import { getDataSourcePlugins } from './state/selectors';
|
||||
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
|
||||
import { setDataSourceTypeSearchQuery } from './state/reducers';
|
||||
import { PluginSignatureBadge } from '../plugins/PluginSignatureBadge';
|
||||
import { Card } from 'app/core/components/Card/Card';
|
||||
import { PluginsErrorsInfo } from '../plugins/PluginsErrorsInfo';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { css } from 'emotion';
|
||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||
import { Icon, IconButton, SetInterval, ToolbarButton, ToolbarButtonRow, Tooltip } from '@grafana/ui';
|
||||
import { DataSourceInstanceSettings, RawTimeRange, TimeRange, TimeZone } from '@grafana/data';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { StoreState } from 'app/types/store';
|
||||
import { createAndCopyShortLink } from 'app/core/utils/shortLinks';
|
||||
import { changeDatasource } from './state/datasource';
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
InputControl,
|
||||
Legend,
|
||||
} from '@grafana/ui';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { DashboardInput, DashboardInputs, DataSourceInput, ImportDashboardDTO } from '../state/reducers';
|
||||
import { validateTitle, validateUid } from '../utils/validation';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from 'react';
|
||||
import { PluginMeta } from '@grafana/data';
|
||||
import { PluginSignatureBadge } from './PluginSignatureBadge';
|
||||
import { PluginSignatureBadge } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
UrlQueryMap,
|
||||
} from '@grafana/data';
|
||||
import { AppNotificationSeverity } from 'app/types';
|
||||
import { Alert, InfoBox, Tooltip } from '@grafana/ui';
|
||||
import { Alert, InfoBox, Tooltip, PluginSignatureBadge } from '@grafana/ui';
|
||||
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { getPluginSettings } from './PluginSettingsCache';
|
||||
import { importAppPlugin, importDataSourcePlugin, importPanelPlugin } from './plugin_loader';
|
||||
@@ -29,7 +30,6 @@ import { appEvents } from 'app/core/core';
|
||||
import { config } from 'app/core/config';
|
||||
import { contextSrv } from '../../core/services/context_srv';
|
||||
import { css } from 'emotion';
|
||||
import { PluginSignatureBadge } from './PluginSignatureBadge';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { ShowModalEvent } from 'app/types/events';
|
||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import React, { HTMLAttributes } from 'react';
|
||||
import { Badge, BadgeProps } from '@grafana/ui';
|
||||
import { PluginErrorCode, PluginSignatureStatus } from '@grafana/data';
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
status?: PluginSignatureStatus;
|
||||
}
|
||||
|
||||
export const PluginSignatureBadge: React.FC<Props> = ({ status, ...otherProps }) => {
|
||||
const display = getSignatureDisplayModel(status);
|
||||
return (
|
||||
<Badge
|
||||
text={display.text}
|
||||
color={display.color as any}
|
||||
icon={display.icon}
|
||||
tooltip={display.tooltip}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export function isUnsignedPluginSignature(signature?: PluginSignatureStatus) {
|
||||
return signature && signature !== PluginSignatureStatus.valid && signature !== PluginSignatureStatus.internal;
|
||||
}
|
||||
|
||||
export function mapPluginErrorCodeToSignatureStatus(code: PluginErrorCode) {
|
||||
switch (code) {
|
||||
case PluginErrorCode.invalidSignature:
|
||||
return PluginSignatureStatus.invalid;
|
||||
case PluginErrorCode.missingSignature:
|
||||
return PluginSignatureStatus.missing;
|
||||
case PluginErrorCode.modifiedSignature:
|
||||
return PluginSignatureStatus.modified;
|
||||
default:
|
||||
return PluginSignatureStatus.missing;
|
||||
}
|
||||
}
|
||||
|
||||
function getSignatureDisplayModel(signature?: PluginSignatureStatus): BadgeProps {
|
||||
if (!signature) {
|
||||
signature = PluginSignatureStatus.invalid;
|
||||
}
|
||||
|
||||
switch (signature) {
|
||||
case PluginSignatureStatus.internal:
|
||||
return { text: 'Core', color: 'blue', tooltip: 'Core plugin that is bundled with Grafana' };
|
||||
case PluginSignatureStatus.valid:
|
||||
return { text: 'Signed', icon: 'lock', color: 'green', tooltip: 'Signed and verified plugin' };
|
||||
case PluginSignatureStatus.invalid:
|
||||
return {
|
||||
text: 'Invalid signature',
|
||||
icon: 'exclamation-triangle',
|
||||
color: 'red',
|
||||
tooltip: 'Invalid plugin signature',
|
||||
};
|
||||
case PluginSignatureStatus.modified:
|
||||
return {
|
||||
text: 'Modified signature',
|
||||
icon: 'exclamation-triangle',
|
||||
color: 'red',
|
||||
tooltip: 'Valid signature but content has been modified',
|
||||
};
|
||||
case PluginSignatureStatus.missing:
|
||||
return {
|
||||
text: 'Missing signature',
|
||||
icon: 'exclamation-triangle',
|
||||
color: 'red',
|
||||
tooltip: 'Missing plugin signature',
|
||||
};
|
||||
}
|
||||
|
||||
return { text: 'Unsigned', icon: 'exclamation-triangle', color: 'red', tooltip: 'Unsigned external plugin' };
|
||||
}
|
||||
|
||||
PluginSignatureBadge.displayName = 'PluginSignatureBadge';
|
||||
@@ -1,14 +1,13 @@
|
||||
import React from 'react';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { HorizontalGroup, InfoBox, List, useTheme } from '@grafana/ui';
|
||||
import { mapPluginErrorCodeToSignatureStatus, PluginSignatureBadge } from './PluginSignatureBadge';
|
||||
import { HorizontalGroup, InfoBox, List, PluginSignatureBadge, useTheme } from '@grafana/ui';
|
||||
import { StoreState } from '../../types';
|
||||
import { getAllPluginsErrors } from './state/selectors';
|
||||
import { loadPlugins, loadPluginsErrors } from './state/actions';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { PluginError } from '@grafana/data';
|
||||
import { PluginError, PluginErrorCode, PluginSignatureStatus } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface ConnectedProps {
|
||||
@@ -102,3 +101,16 @@ const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||
export const PluginsErrorsInfo = hot(module)(
|
||||
connect(mapStateToProps, mapDispatchToProps)(PluginsErrorsInfoUnconnected)
|
||||
);
|
||||
|
||||
function mapPluginErrorCodeToSignatureStatus(code: PluginErrorCode) {
|
||||
switch (code) {
|
||||
case PluginErrorCode.invalidSignature:
|
||||
return PluginSignatureStatus.invalid;
|
||||
case PluginErrorCode.missingSignature:
|
||||
return PluginSignatureStatus.missing;
|
||||
case PluginErrorCode.modifiedSignature:
|
||||
return PluginSignatureStatus.modified;
|
||||
default:
|
||||
return PluginSignatureStatus.missing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
// Components
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { Button, CustomScrollbar, HorizontalGroup, Icon, Modal, stylesFactory, Tooltip } from '@grafana/ui';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { getDataSourceSrv, DataSourcePicker } from '@grafana/runtime';
|
||||
import { QueryEditorRows } from './QueryEditorRows';
|
||||
// Services
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css } from 'emotion';
|
||||
import { MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||
import { InlineField, InlineFieldRow, VerticalGroup } from '@grafana/ui';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { getTemplateSrv, DataSourcePicker } from '@grafana/runtime';
|
||||
import { DataSourceInstanceSettings, LoadingState, SelectableValue } from '@grafana/data';
|
||||
|
||||
import { SelectionOptionsEditor } from '../editor/SelectionOptionsEditor';
|
||||
@@ -23,7 +23,6 @@ import { VariableTextField } from '../editor/VariableTextField';
|
||||
import { VariableSwitchField } from '../editor/VariableSwitchField';
|
||||
import { QueryVariableRefreshSelect } from './QueryVariableRefreshSelect';
|
||||
import { QueryVariableSortSelect } from './QueryVariableSortSelect';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
|
||||
export interface OwnProps extends VariableEditorProps<QueryVariableModel> {}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { VariableSuggestion } from '@grafana/data';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { Button, LegacyForms, DataLinkInput, stylesFactory } from '@grafana/ui';
|
||||
const { FormField, Switch } = LegacyForms;
|
||||
import { DataLinkConfig } from '../types';
|
||||
import { usePrevious } from 'react-use';
|
||||
import { DataSourcePicker } from '../../../../core/components/Select/DataSourcePicker';
|
||||
|
||||
const getStyles = stylesFactory(() => ({
|
||||
firstRow: css`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { DerivedField } from './DerivedField';
|
||||
import { DataSourcePicker } from '../../../../core/components/Select/DataSourcePicker';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
|
||||
jest.mock('app/features/plugins/datasource_srv', () => ({
|
||||
|
||||
@@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { Button, DataLinkInput, stylesFactory, LegacyForms } from '@grafana/ui';
|
||||
import { VariableSuggestion } from '@grafana/data';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { DerivedFieldConfig } from '../types';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { usePrevious } from 'react-use';
|
||||
|
||||
const { Switch, FormField } = LegacyForms;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, InlineField, InlineSwitch, Input } from '@grafana/ui';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { DataSourcePicker } from '@grafana/runtime';
|
||||
import { css } from 'emotion';
|
||||
import React, { useState } from 'react';
|
||||
import { ExemplarTraceIdDestination } from '../types';
|
||||
|
||||
Reference in New Issue
Block a user