mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Datasource: datasource config page header redesign (#66999)
* Wip * remove name input from page body * render title & subTitle in datasource page in connections and remove unused prop - uid * styling on actions button group * remove unused props in edit datasource page * set gap as 8px between buttons * move editable datasourcename to the header * add subTitle component * remove useRef and use autoFocus prop * pass false to isDefault when dataSource is undefined * change button text * remove suffix icon * remove unused import - Icon * consolidate duplicate useDataSourceSettingsNav into datasrouces hook * move dataSource context to useDataSourceSettingsNav * remove Explore button in the footer * remove unused props * fix failing test on button group * fix typo on file naming * remove disabled prop * fix param * add test * add test files * disable editing title in readOnly provision datasource * update name should save dataSource * prevent swith toggle change from label clicking and change margin * update tooltip message * use datasource update on header instead of state update * remvoe subTitle component and move subTitle component next to page Info component * Added title * remove subTitle in buildNavModel * replace Button with Badge * make datasourceheader as a component * horizontal gap of 24px between pageInfo and actions components * align page Info value items * accept react node as page info label and add tooltip to Default item * update navId for edit datasource page in connections * update unit testing for Title * fix gen_que * betterer * prettier fix * fix e2e test * add data-testid to nameEditIcon selector * fix tooltip text * fix navId for connections datasources edit page * fix e2e selector: change autoSizeInput to Input * adding ellipsis to EditDataSourceTitle * override grafana-ui titleContainer h1 styles * UI cleanup and apply readOnly to default datasource switch * add period * datasource name validation * title and page info alignment * add feature toggle - dataSourcePageHeader * restore basicSettings component and apply feature toggle * go lint * Revert "title and page info alignment" This reverts commit681ac51f11
. * remove editable fields from page Header - name, default datasource switch * fix go test: toggle generator * remove test id * remove alerting badge in BasicSettings component * Revert "remove alerting badge in BasicSettings component" This reverts commitfb33ff9028
. * feature toggle on alerting badge * rename component & filename * move DataSourceInfo type * change button to link in test --------- Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com> Co-authored-by: Miguel Palau Zarza <mpalauzarza@gmail.com>
This commit is contained in:
parent
7f61cb92bc
commit
7f84e83ffe
@ -63,6 +63,7 @@ Some stable features are enabled by default. You can disable a stable feature by
|
||||
| `enableElasticsearchBackendQuerying` | Enable the processing of queries and responses in the Elasticsearch data source through backend |
|
||||
| `faroDatasourceSelector` | Enable the data source selector within the Frontend Apps section of the Frontend Observability |
|
||||
| `enableDatagridEditing` | Enables the edit functionality in the datagrid panel |
|
||||
| `dataSourcePageHeader` | Apply new pageHeader UI in data source edit page |
|
||||
|
||||
## Alpha feature toggles
|
||||
|
||||
|
@ -95,5 +95,6 @@ export interface FeatureToggles {
|
||||
advancedDataSourcePicker?: boolean;
|
||||
faroDatasourceSelector?: boolean;
|
||||
enableDatagridEditing?: boolean;
|
||||
dataSourcePageHeader?: boolean;
|
||||
extraThemes?: boolean;
|
||||
}
|
||||
|
@ -24,4 +24,5 @@ const (
|
||||
appO11ySquad codeowner = "@grafana/app-o11y"
|
||||
grafanaPartnerPluginsSquad codeowner = "@grafana/partner-plugins"
|
||||
grafanaOperatorExperienceSquad codeowner = "@grafana/grafana-operator-experience-squad"
|
||||
enterpriseDatasourcesSquad codeowner = "@grafana/enterprise-datasources"
|
||||
)
|
||||
|
@ -519,6 +519,13 @@ var (
|
||||
State: FeatureStateBeta,
|
||||
Owner: grafanaBiSquad,
|
||||
},
|
||||
{
|
||||
Name: "dataSourcePageHeader",
|
||||
Description: "Apply new pageHeader UI in data source edit page",
|
||||
FrontendOnly: true,
|
||||
State: FeatureStateBeta,
|
||||
Owner: enterpriseDatasourcesSquad,
|
||||
},
|
||||
{
|
||||
Name: "extraThemes",
|
||||
Description: "Enables extra themes",
|
||||
|
@ -76,4 +76,5 @@ pluginsAPIManifestKey,alpha,@grafana/plugins-platform-backend,false,false,false,
|
||||
advancedDataSourcePicker,stable,@grafana/dashboards-squad,false,false,false,true
|
||||
faroDatasourceSelector,beta,@grafana/app-o11y,false,false,false,true
|
||||
enableDatagridEditing,beta,@grafana/grafana-bi-squad,false,false,false,true
|
||||
dataSourcePageHeader,beta,@grafana/enterprise-datasources,false,false,false,true
|
||||
extraThemes,alpha,@grafana/user-essentials,false,false,false,true
|
||||
|
|
@ -315,6 +315,10 @@ const (
|
||||
// Enables the edit functionality in the datagrid panel
|
||||
FlagEnableDatagridEditing = "enableDatagridEditing"
|
||||
|
||||
// FlagDataSourcePageHeader
|
||||
// Apply new pageHeader UI in data source edit page
|
||||
FlagDataSourcePageHeader = "dataSourcePageHeader"
|
||||
|
||||
// FlagExtraThemes
|
||||
// Enables extra themes
|
||||
FlagExtraThemes = "extraThemes"
|
||||
|
@ -46,7 +46,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
gap: theme.spacing(1, 2),
|
||||
gap: theme.spacing(1, 3),
|
||||
}),
|
||||
title: css({
|
||||
display: 'flex',
|
||||
|
@ -3,15 +3,16 @@ import { useParams } from 'react-router-dom';
|
||||
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { DataSourceDashboards } from 'app/features/datasources/components/DataSourceDashboards';
|
||||
|
||||
import { useDataSourceSettingsNav } from '../hooks/useDataSourceSettingsNav';
|
||||
import { useDataSourceSettingsNav } from 'app/features/datasources/state';
|
||||
|
||||
export function DataSourceDashboardsPage() {
|
||||
const { uid } = useParams<{ uid: string }>();
|
||||
const { navId, pageNav } = useDataSourceSettingsNav('dashboards');
|
||||
const params = new URLSearchParams(location.search);
|
||||
const pageId = params.get('page');
|
||||
const nav = useDataSourceSettingsNav(uid, pageId);
|
||||
|
||||
return (
|
||||
<Page navId={navId} pageNav={pageNav}>
|
||||
<Page navId="connections-your-connections-datasources" pageNav={nav.main}>
|
||||
<Page.Contents>
|
||||
<DataSourceDashboards uid={uid} />
|
||||
</Page.Contents>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import DataSourceTabPage from 'app/features/datasources/components/DataSourceTabPage';
|
||||
import { EditDataSource } from 'app/features/datasources/components/EditDataSource';
|
||||
import { EditDataSourceActions } from 'app/features/datasources/components/EditDataSourceActions';
|
||||
|
||||
@ -12,8 +14,13 @@ export function EditDataSourcePage() {
|
||||
const location = useLocation();
|
||||
const params = new URLSearchParams(location.search);
|
||||
const pageId = params.get('page');
|
||||
const dataSourcePageHeader = config.featureToggles.dataSourcePageHeader;
|
||||
const { navId, pageNav } = useDataSourceSettingsNav();
|
||||
|
||||
if (dataSourcePageHeader) {
|
||||
return <DataSourceTabPage uid={uid} pageId={pageId} navId="connections-datasources" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page navId={navId} pageNav={pageNav} actions={<EditDataSourceActions uid={uid} />}>
|
||||
<Page.Contents>
|
||||
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { InlineField, InlineSwitch, Input, Badge, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
@ -24,7 +25,7 @@ export function BasicSettings({
|
||||
}: Props) {
|
||||
return (
|
||||
<>
|
||||
<AlertingEnabled enabled={alertingSupported} />
|
||||
{!config.featureToggles.dataSourcePageHeader && <AlertingEnabled enabled={alertingSupported} />}
|
||||
|
||||
<div className="gf-form-group" aria-label="Datasource settings page basic settings">
|
||||
<div className="gf-form-inline">
|
||||
|
@ -26,6 +26,7 @@ describe('<ButtonRow>', () => {
|
||||
expect(screen.getByTestId(selectors.pages.DataSource.delete)).toBeInTheDocument();
|
||||
expect(screen.getByText('Test')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render save & test', () => {
|
||||
setup({ canSave: true });
|
||||
|
||||
|
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { EditDataSource } from '../components/EditDataSource';
|
||||
import { EditDataSourceActions } from '../components/EditDataSourceActions';
|
||||
import { useDataSourceInfo } from '../components/useDataSourceInfo';
|
||||
import { useDataSourceSettingsNav } from '../state';
|
||||
|
||||
import { DataSourceTitle } from './DataSourceTitle';
|
||||
|
||||
export interface Props {
|
||||
uid: string;
|
||||
pageId: string | null;
|
||||
navId: string;
|
||||
}
|
||||
|
||||
export function DataSourceTabPage({ uid, pageId, navId }: Props) {
|
||||
const nav = useDataSourceSettingsNav(uid, pageId);
|
||||
|
||||
const info = useDataSourceInfo({
|
||||
dataSourcePluginName: nav.main.dataSourcePluginName,
|
||||
alertingSupported: nav.dataSourceHeader.alertingSupported,
|
||||
});
|
||||
|
||||
return (
|
||||
<Page
|
||||
navId={navId}
|
||||
pageNav={nav.main}
|
||||
renderTitle={(title) => <DataSourceTitle title={title} />}
|
||||
info={info}
|
||||
actions={<EditDataSourceActions uid={uid} />}
|
||||
>
|
||||
<Page.Contents>
|
||||
<EditDataSource uid={uid} pageId={pageId} />
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default DataSourceTabPage;
|
@ -0,0 +1,38 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function DataSourceTitle({ title }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>{title}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
container: css({
|
||||
marginBottom: theme.spacing(2),
|
||||
h1: {
|
||||
display: 'inline-block',
|
||||
},
|
||||
}),
|
||||
title: css({
|
||||
display: 'inline-block',
|
||||
margin: '0 0 0 0',
|
||||
maxWidth: '40vw',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}),
|
||||
};
|
||||
};
|
@ -1,9 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { LinkButton } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
@ -11,28 +9,35 @@ import { useDataSource } from '../state';
|
||||
import { trackCreateDashboardClicked, trackExploreClicked } from '../tracking';
|
||||
import { constructDataSourceExploreUrl } from '../utils';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
button: css({
|
||||
marginLeft: theme.spacing(2),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
uid: string;
|
||||
}
|
||||
|
||||
export function EditDataSourceActions({ uid }: Props) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const dataSource = useDataSource(uid);
|
||||
const hasExploreRights = contextSrv.hasPermission(AccessControlAction.DataSourcesExplore);
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasExploreRights && (
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
href={constructDataSourceExploreUrl(dataSource)}
|
||||
onClick={() => {
|
||||
trackExploreClicked({
|
||||
grafana_version: config.buildInfo.version,
|
||||
datasource_uid: dataSource.uid,
|
||||
plugin_name: dataSource.typeName,
|
||||
path: location.pathname,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Explore data
|
||||
</LinkButton>
|
||||
)}
|
||||
<LinkButton
|
||||
icon="apps"
|
||||
fill="outline"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
href={`dashboard/new-with-ds/${dataSource.uid}`}
|
||||
onClick={() => {
|
||||
@ -46,26 +51,6 @@ export function EditDataSourceActions({ uid }: Props) {
|
||||
>
|
||||
Build a dashboard
|
||||
</LinkButton>
|
||||
|
||||
{hasExploreRights && (
|
||||
<LinkButton
|
||||
icon="compass"
|
||||
fill="outline"
|
||||
variant="secondary"
|
||||
className={styles.button}
|
||||
href={constructDataSourceExploreUrl(dataSource)}
|
||||
onClick={() => {
|
||||
trackExploreClicked({
|
||||
grafana_version: config.buildInfo.version,
|
||||
datasource_uid: dataSource.uid,
|
||||
plugin_name: dataSource.typeName,
|
||||
path: location.pathname,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Explore
|
||||
</LinkButton>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Badge } from '@grafana/ui';
|
||||
import { PageInfoItem } from 'app/core/components/Page/types';
|
||||
|
||||
type DataSourceInfo = {
|
||||
dataSourcePluginName: string;
|
||||
alertingSupported: boolean;
|
||||
};
|
||||
|
||||
export const useDataSourceInfo = (dataSourceInfo: DataSourceInfo): PageInfoItem[] => {
|
||||
const info: PageInfoItem[] = [];
|
||||
const alertingEnabled = dataSourceInfo.alertingSupported;
|
||||
|
||||
info.push({
|
||||
label: 'Type',
|
||||
value: dataSourceInfo.dataSourcePluginName,
|
||||
});
|
||||
|
||||
info.push({
|
||||
label: 'Alerting',
|
||||
value: (
|
||||
<Badge color={alertingEnabled ? 'green' : 'red'} text={alertingEnabled ? 'Supported' : 'Not supported'}></Badge>
|
||||
),
|
||||
});
|
||||
|
||||
return info;
|
||||
};
|
@ -116,9 +116,8 @@ describe('<EditDataSourcePage>', () => {
|
||||
await waitFor(() => {
|
||||
// Buttons
|
||||
expect(screen.queryByRole('button', { name: /Delete/i })).toBeVisible();
|
||||
expect(screen.queryByRole('button', { name: /Save (.*) test/i })).toBeVisible();
|
||||
expect(screen.queryByRole('link', { name: /Build a dashboard/i })).toBeVisible();
|
||||
expect(screen.queryAllByRole('link', { name: /Explore/i })).toHaveLength(1);
|
||||
expect(screen.queryAllByRole('link', { name: /Explore data/i })).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||
|
||||
import DataSourceTabPage from '../components/DataSourceTabPage';
|
||||
import { EditDataSource } from '../components/EditDataSource';
|
||||
import { EditDataSourceActions } from '../components/EditDataSourceActions';
|
||||
import { useDataSourceSettingsNav } from '../state';
|
||||
@ -13,8 +15,13 @@ export function EditDataSourcePage(props: Props) {
|
||||
const uid = props.match.params.uid;
|
||||
const params = new URLSearchParams(props.location.search);
|
||||
const pageId = params.get('page');
|
||||
const dataSourcePageHeader = config.featureToggles.dataSourcePageHeader;
|
||||
const nav = useDataSourceSettingsNav(uid, pageId);
|
||||
|
||||
if (dataSourcePageHeader) {
|
||||
return <DataSourceTabPage uid={uid} pageId={pageId} navId="datasources" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page navId="datasources" pageNav={nav.main} actions={<EditDataSourceActions uid={uid} />}>
|
||||
<Page.Contents>
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
|
||||
import { DataSourcePluginMeta, DataSourceSettings, NavModelItem } from '@grafana/data';
|
||||
import { DataSourcePluginMeta, DataSourceSettings, NavModel, NavModelItem } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { cleanUpAction } from 'app/core/actions/cleanUp';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { useGetSingle } from 'app/features/plugins/admin/state/hooks';
|
||||
import { AccessControlAction, useDispatch, useSelector } from 'app/types';
|
||||
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||
|
||||
@ -127,10 +129,24 @@ export const useDataSourceSettings = () => {
|
||||
};
|
||||
|
||||
export const useDataSourceSettingsNav = (dataSourceId: string, pageId: string | null) => {
|
||||
const dataSource = useDataSource(dataSourceId);
|
||||
const { plugin, loadError, loading } = useDataSourceSettings();
|
||||
const dataSource = useDataSource(dataSourceId);
|
||||
const dsi = getDataSourceSrv()?.getInstanceSettings(dataSourceId);
|
||||
const hasAlertingEnabled = Boolean(dsi?.meta?.alerting ?? false);
|
||||
const isAlertManagerDatasource = dsi?.type === 'alertmanager';
|
||||
const alertingSupported = hasAlertingEnabled || isAlertManagerDatasource;
|
||||
|
||||
const datasourcePlugin = useGetSingle(dataSource.type);
|
||||
const navIndex = useSelector((state) => state.navIndex);
|
||||
const navIndexId = pageId ? `datasource-${pageId}-${dataSourceId}` : `datasource-settings-${dataSourceId}`;
|
||||
let pageNav: NavModel = {
|
||||
node: {
|
||||
text: 'Data Source Nav Node',
|
||||
},
|
||||
main: {
|
||||
text: 'Data Source Nav Node',
|
||||
},
|
||||
};
|
||||
|
||||
if (loadError) {
|
||||
const node: NavModelItem = {
|
||||
@ -139,17 +155,36 @@ export const useDataSourceSettingsNav = (dataSourceId: string, pageId: string |
|
||||
icon: 'exclamation-triangle',
|
||||
};
|
||||
|
||||
return {
|
||||
pageNav = {
|
||||
node: node,
|
||||
main: node,
|
||||
};
|
||||
}
|
||||
|
||||
if (loading || !plugin) {
|
||||
return getNavModel(navIndex, navIndexId, getDataSourceLoadingNav('settings'));
|
||||
pageNav = getNavModel(navIndex, navIndexId, getDataSourceLoadingNav('settings'));
|
||||
}
|
||||
|
||||
return getNavModel(navIndex, navIndexId, getDataSourceNav(buildNavModel(dataSource, plugin), pageId || 'settings'));
|
||||
if (plugin) {
|
||||
pageNav = getNavModel(
|
||||
navIndex,
|
||||
navIndexId,
|
||||
getDataSourceNav(buildNavModel(dataSource, plugin), pageId || 'settings')
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
node: pageNav.node,
|
||||
main: {
|
||||
...pageNav.main,
|
||||
text: dataSource.name,
|
||||
dataSourcePluginName: datasourcePlugin?.name || '',
|
||||
active: true,
|
||||
},
|
||||
dataSourceHeader: {
|
||||
alertingSupported,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useDataSourceRights = (uid: string): DataSourceRights => {
|
||||
|
@ -16,7 +16,6 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat
|
||||
const navModel: NavModelItem = {
|
||||
img: pluginMeta.info.logos.large,
|
||||
id: 'datasource-' + dataSource.uid,
|
||||
subTitle: `Type: ${pluginMeta.name}`,
|
||||
url: '',
|
||||
text: dataSource.name,
|
||||
children: [
|
||||
|
Loading…
Reference in New Issue
Block a user