mirror of https://github.com/grafana/grafana
Connections: Support standalone plugin pages (#57772)
* feat(Connections): create sub-pages wrapped by `<Page>` * feat(Connections): rename the Connections page and update routes * feat(Connections): use new url for editing datasources * refactor(Connections): remove unused tab components * feat(Connections): update routes and nav titles * tests: fix tests for Connections * tests: fix typo in backend testspull/58572/head
parent
c5ae1bcfe0
commit
181640b080
@ -0,0 +1,89 @@ |
||||
import { render, RenderResult, screen } from '@testing-library/react'; |
||||
import React from 'react'; |
||||
import { Provider } from 'react-redux'; |
||||
import { Router } from 'react-router-dom'; |
||||
|
||||
import { locationService } from '@grafana/runtime'; |
||||
import { getMockDataSources } from 'app/features/datasources/__mocks__'; |
||||
import * as api from 'app/features/datasources/api'; |
||||
import { configureStore } from 'app/store/configureStore'; |
||||
|
||||
import { getPluginsStateMock } from '../plugins/admin/__mocks__'; |
||||
|
||||
import Connections from './Connections'; |
||||
import { navIndex } from './__mocks__/store.navIndex.mock'; |
||||
import { ROUTE_BASE_ID, ROUTES } from './constants'; |
||||
|
||||
jest.mock('app/features/datasources/api'); |
||||
|
||||
const renderPage = ( |
||||
path = `/${ROUTE_BASE_ID}`, |
||||
store = configureStore({ navIndex, plugins: getPluginsStateMock([]) }) |
||||
): RenderResult => { |
||||
locationService.push(path); |
||||
|
||||
return render( |
||||
<Provider store={store}> |
||||
<Router history={locationService.getHistory()}> |
||||
<Connections /> |
||||
</Router> |
||||
</Provider> |
||||
); |
||||
}; |
||||
|
||||
describe('Connections', () => { |
||||
const mockDatasources = getMockDataSources(3); |
||||
|
||||
beforeEach(() => { |
||||
(api.getDataSources as jest.Mock) = jest.fn().mockResolvedValue(mockDatasources); |
||||
}); |
||||
|
||||
test('shows the "Data sources" page by default', async () => { |
||||
renderPage(); |
||||
|
||||
expect(await screen.findByText('Datasources')).toBeVisible(); |
||||
expect(await screen.findByText('Manage your existing datasource connections')).toBeVisible(); |
||||
expect(await screen.findByRole('link', { name: /add data source/i })).toBeVisible(); |
||||
expect(await screen.findByText(mockDatasources[0].name)).toBeVisible(); |
||||
}); |
||||
|
||||
test('renders the correct tab even if accessing it with a "sub-url"', async () => { |
||||
renderPage(ROUTES.ConnectData); |
||||
|
||||
expect(await screen.findByText('Connect data')).toBeVisible(); |
||||
expect(await screen.findByText('Browse and create new connections')).toBeVisible(); |
||||
|
||||
// Should not render the "Your datasources" page
|
||||
expect(screen.queryByText('Manage your existing datasource connections')).not.toBeInTheDocument(); |
||||
}); |
||||
|
||||
test('renders the "Connect data" page using a plugin in case it is a standalone plugin page', async () => { |
||||
// We are overriding the navIndex to have the "Connect data" page registered by a plugin
|
||||
const standalonePluginPage = { |
||||
id: 'standalone-plugin-page-/connections/connect-data', |
||||
text: 'Connect data', |
||||
subTitle: 'Browse and create new connections', |
||||
url: '/connections/connect-data', |
||||
pluginId: 'grafana-easystart-app', |
||||
}; |
||||
const connections = { |
||||
...navIndex.connections, |
||||
children: navIndex.connections.children?.map((child) => { |
||||
if (child.id === 'connections-connect-data') { |
||||
return standalonePluginPage; |
||||
} |
||||
|
||||
return child; |
||||
}), |
||||
}; |
||||
const store = configureStore({ |
||||
navIndex: { ...navIndex, connections, [standalonePluginPage.id]: standalonePluginPage }, |
||||
plugins: getPluginsStateMock([]), |
||||
}); |
||||
|
||||
renderPage(ROUTES.ConnectData, store); |
||||
|
||||
// We expect not to see the same text as if it was rendered by core.
|
||||
expect(screen.queryByText('No results matching your query were found.')).not.toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1,43 @@ |
||||
import * as React from 'react'; |
||||
import { Route, Switch } from 'react-router-dom'; |
||||
|
||||
import { DataSourcesRoutesContext } from 'app/features/datasources/state'; |
||||
import { StoreState, useSelector } from 'app/types'; |
||||
|
||||
import { ROUTES } from './constants'; |
||||
import { |
||||
ConnectDataPage, |
||||
DataSourceDetailsPage, |
||||
DataSourcesListPage, |
||||
EditDataSourcePage, |
||||
NewDataSourcePage, |
||||
} from './pages'; |
||||
|
||||
export default function Connections() { |
||||
const navIndex = useSelector((state: StoreState) => state.navIndex); |
||||
const isConnectDataPageOverriden = Boolean(navIndex['standalone-plugin-page-/connections/connect-data']); |
||||
|
||||
return ( |
||||
<DataSourcesRoutesContext.Provider |
||||
value={{ |
||||
New: ROUTES.DataSourcesNew, |
||||
List: ROUTES.DataSources, |
||||
Edit: ROUTES.DataSourcesEdit, |
||||
Dashboards: ROUTES.DataSourcesDashboards, |
||||
}} |
||||
> |
||||
<Switch> |
||||
<Route exact path={ROUTES.Base} component={DataSourcesListPage} /> |
||||
<Route exact path={ROUTES.YourConnections} component={DataSourcesListPage} /> |
||||
<Route exact path={ROUTES.DataSources} component={DataSourcesListPage} /> |
||||
<Route exact path={ROUTES.DataSourcesDetails} component={DataSourceDetailsPage} /> |
||||
<Route exact path={ROUTES.DataSourcesNew} component={NewDataSourcePage} /> |
||||
<Route exact path={ROUTES.DataSourcesEdit} component={EditDataSourcePage} /> |
||||
{!isConnectDataPageOverriden && <Route path={ROUTES.ConnectData} component={ConnectDataPage} />} |
||||
|
||||
{/* Default page */} |
||||
<Route component={DataSourcesListPage} /> |
||||
</Switch> |
||||
</DataSourcesRoutesContext.Provider> |
||||
); |
||||
} |
@ -1,59 +0,0 @@ |
||||
import { render, RenderResult, screen } from '@testing-library/react'; |
||||
import React from 'react'; |
||||
import { Provider } from 'react-redux'; |
||||
import { Router } from 'react-router-dom'; |
||||
|
||||
import { locationService } from '@grafana/runtime'; |
||||
import { getMockDataSources } from 'app/features/datasources/__mocks__'; |
||||
import * as api from 'app/features/datasources/api'; |
||||
import { configureStore } from 'app/store/configureStore'; |
||||
|
||||
import { getPluginsStateMock } from '../plugins/admin/__mocks__'; |
||||
|
||||
import ConnectionsPage from './ConnectionsPage'; |
||||
import { navIndex } from './__mocks__/store.navIndex.mock'; |
||||
import { ROUTE_BASE_ID, ROUTES } from './constants'; |
||||
|
||||
jest.mock('app/features/datasources/api'); |
||||
|
||||
const renderPage = (path = `/${ROUTE_BASE_ID}`): RenderResult => { |
||||
// @ts-ignore
|
||||
const store = configureStore({ navIndex, plugins: getPluginsStateMock([]) }); |
||||
locationService.push(path); |
||||
|
||||
return render( |
||||
<Provider store={store}> |
||||
<Router history={locationService.getHistory()}> |
||||
<ConnectionsPage /> |
||||
</Router> |
||||
</Provider> |
||||
); |
||||
}; |
||||
|
||||
describe('Connections Page', () => { |
||||
const mockDatasources = getMockDataSources(3); |
||||
|
||||
beforeEach(() => { |
||||
(api.getDataSources as jest.Mock) = jest.fn().mockResolvedValue(mockDatasources); |
||||
}); |
||||
|
||||
test('shows all tabs', async () => { |
||||
renderPage(); |
||||
|
||||
expect(await screen.findByLabelText('Tab Data sources')).toBeVisible(); |
||||
expect(await screen.findByLabelText('Tab Connect Data')).toBeVisible(); |
||||
}); |
||||
|
||||
test('shows the "Data sources" tab by default', async () => { |
||||
renderPage(); |
||||
|
||||
expect(await screen.findByRole('link', { name: /add data source/i })).toBeVisible(); |
||||
expect(await screen.findByText(mockDatasources[0].name)).toBeVisible(); |
||||
}); |
||||
|
||||
test('renders the correct tab even if accessing it with a "sub-url"', async () => { |
||||
renderPage(`${ROUTES.ConnectData}`); |
||||
|
||||
expect(screen.queryByText('No results matching your query were found.')).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -1,41 +0,0 @@ |
||||
import * as React from 'react'; |
||||
import { Route, Switch } from 'react-router-dom'; |
||||
|
||||
import { Page } from 'app/core/components/Page/Page'; |
||||
import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList'; |
||||
import { NewDataSource } from 'app/features/datasources/components/NewDataSource'; |
||||
import { DataSourcesRoutesContext } from 'app/features/datasources/state'; |
||||
|
||||
import { ROUTES } from './constants'; |
||||
import { useNavModel } from './hooks/useNavModel'; |
||||
import { ConnectData } from './tabs/ConnectData'; |
||||
import { DataSourcesEdit } from './tabs/DataSourcesEdit'; |
||||
|
||||
export default function ConnectionsPage() { |
||||
const navModel = useNavModel(); |
||||
|
||||
return ( |
||||
<DataSourcesRoutesContext.Provider |
||||
value={{ |
||||
New: ROUTES.DataSourcesNew, |
||||
List: ROUTES.DataSources, |
||||
Edit: ROUTES.DataSourcesEdit, |
||||
Dashboards: ROUTES.DataSourcesDashboards, |
||||
}} |
||||
> |
||||
<Page navModel={navModel}> |
||||
<Page.Contents> |
||||
<Switch> |
||||
<Route path={ROUTES.DataSourcesNew} component={NewDataSource} /> |
||||
<Route path={ROUTES.DataSourcesEdit} component={DataSourcesEdit} /> |
||||
<Route path={ROUTES.DataSources} component={DataSourcesList} /> |
||||
<Route path={ROUTES.ConnectData} component={ConnectData} /> |
||||
|
||||
{/* Default page */} |
||||
<Route component={DataSourcesList} /> |
||||
</Switch> |
||||
</Page.Contents> |
||||
</Page> |
||||
</DataSourcesRoutesContext.Provider> |
||||
); |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@ |
||||
import * as React from 'react'; |
||||
|
||||
import { Page } from 'app/core/components/Page/Page'; |
||||
|
||||
import { ConnectData } from '../tabs/ConnectData'; |
||||
|
||||
export function ConnectDataPage() { |
||||
return ( |
||||
<Page navId={'connections-connect-data'}> |
||||
<Page.Contents> |
||||
<ConnectData /> |
||||
</Page.Contents> |
||||
</Page> |
||||
); |
||||
} |
@ -0,0 +1,24 @@ |
||||
import * as React from 'react'; |
||||
|
||||
import { Page } from 'app/core/components/Page/Page'; |
||||
import { StoreState, useSelector } from 'app/types'; |
||||
|
||||
export function DataSourceDetailsPage() { |
||||
const overrideNavId = 'standalone-plugin-page-/connections/connect-data'; |
||||
const navIndex = useSelector((state: StoreState) => state.navIndex); |
||||
const isConnectDataPageOverriden = Boolean(navIndex[overrideNavId]); |
||||
const navId = isConnectDataPageOverriden ? overrideNavId : 'connections-connect-data'; // The nav id changes (gets a prefix) if it is overriden by a plugin
|
||||
|
||||
return ( |
||||
<Page |
||||
navId={navId} |
||||
pageNav={{ |
||||
text: 'Datasource details', |
||||
subTitle: 'This is going to be the details page for a datasource', |
||||
active: true, |
||||
}} |
||||
> |
||||
<Page.Contents>Data Source Details (no exposed component from plugins yet)</Page.Contents> |
||||
</Page> |
||||
); |
||||
} |
@ -0,0 +1,14 @@ |
||||
import * as React from 'react'; |
||||
|
||||
import { Page } from 'app/core/components/Page/Page'; |
||||
import { DataSourcesList } from 'app/features/datasources/components/DataSourcesList'; |
||||
|
||||
export function DataSourcesListPage() { |
||||
return ( |
||||
<Page navId={'connections-your-connections-datasources'}> |
||||
<Page.Contents> |
||||
<DataSourcesList /> |
||||
</Page.Contents> |
||||
</Page> |
||||
); |
||||
} |
@ -0,0 +1,27 @@ |
||||
import * as React from 'react'; |
||||
import { useLocation, useParams } from 'react-router-dom'; |
||||
|
||||
import { Page } from 'app/core/components/Page/Page'; |
||||
import { EditDataSource } from 'app/features/datasources/components/EditDataSource'; |
||||
import { useDataSource } from 'app/features/datasources/state/hooks'; |
||||
import { useGetSingle } from 'app/features/plugins/admin/state/hooks'; |
||||
|
||||
export function EditDataSourcePage() { |
||||
const { uid } = useParams<{ uid: string }>(); |
||||
const location = useLocation(); |
||||
const datasource = useDataSource(uid); |
||||
const datasourcePlugin = useGetSingle(datasource.type); |
||||
const params = new URLSearchParams(location.search); |
||||
const pageId = params.get('page'); |
||||
|
||||
return ( |
||||
<Page |
||||
navId={'connections-your-connections-datasources'} |
||||
pageNav={{ text: datasource.name, subTitle: `Type: ${datasourcePlugin?.name}`, active: true }} |
||||
> |
||||
<Page.Contents> |
||||
<EditDataSource uid={uid} pageId={pageId} /> |
||||
</Page.Contents> |
||||
</Page> |
||||
); |
||||
} |
@ -0,0 +1,17 @@ |
||||
import * as React from 'react'; |
||||
|
||||
import { Page } from 'app/core/components/Page/Page'; |
||||
import { NewDataSource } from 'app/features/datasources/components/NewDataSource'; |
||||
|
||||
export function NewDataSourcePage() { |
||||
return ( |
||||
<Page |
||||
navId={'connections-your-connections-datasources'} |
||||
pageNav={{ text: 'Add data source', subTitle: 'Choose a data source type', active: true }} |
||||
> |
||||
<Page.Contents> |
||||
<NewDataSource /> |
||||
</Page.Contents> |
||||
</Page> |
||||
); |
||||
} |
@ -0,0 +1,5 @@ |
||||
export { ConnectDataPage } from './ConnectDataPage'; |
||||
export { DataSourceDetailsPage } from './DataSourceDetailsPage'; |
||||
export { DataSourcesListPage } from './DataSourcesListPage'; |
||||
export { EditDataSourcePage } from './EditDataSourcePage'; |
||||
export { NewDataSourcePage } from './NewDataSourcePage'; |
@ -1,14 +0,0 @@ |
||||
import React from 'react'; |
||||
|
||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; |
||||
import { EditDataSource } from 'app/features/datasources/components/EditDataSource'; |
||||
|
||||
export interface Props extends GrafanaRouteComponentProps<{ uid: string }> {} |
||||
|
||||
export function DataSourcesEdit(props: Props) { |
||||
const uid = props.match.params.uid; |
||||
const params = new URLSearchParams(props.location.search); |
||||
const pageId = params.get('page'); |
||||
|
||||
return <EditDataSource uid={uid} pageId={pageId} />; |
||||
} |
@ -1 +0,0 @@ |
||||
export * from './DataSourcesEdit'; |
Loading…
Reference in new issue