diff --git a/public/app/features/datasources/components/DataSourcesList.tsx b/public/app/features/datasources/components/DataSourcesList.tsx
index 8d92074cc47..6cad171ec84 100644
--- a/public/app/features/datasources/components/DataSourcesList.tsx
+++ b/public/app/features/datasources/components/DataSourcesList.tsx
@@ -4,16 +4,15 @@ import { useLocation } from 'react-router-dom';
import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
-import { LinkButton, Card, Tag, useStyles2 } from '@grafana/ui';
+import { useStyles2 } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
-import PageLoader from 'app/core/components/PageLoader/PageLoader';
import { contextSrv } from 'app/core/core';
import { StoreState, AccessControlAction, useSelector } from 'app/types';
import { getDataSources, getDataSourcesCount, useDataSourcesRoutes, useLoadDataSources } from '../state';
-import { trackCreateDashboardClicked, trackExploreClicked, trackDataSourcesListViewed } from '../tracking';
-import { constructDataSourceExploreUrl } from '../utils';
+import { trackDataSourcesListViewed } from '../tracking';
+import { DataSourcesListCard } from './DataSourcesListCard';
import { DataSourcesListHeader } from './DataSourcesListHeader';
export function DataSourcesList() {
@@ -65,11 +64,7 @@ export function DataSourcesListView({
});
}, [location]);
- if (isLoading) {
- return ;
- }
-
- if (dataSourcesCount === 0) {
+ if (!isLoading && dataSourcesCount === 0) {
return (
{
+ if (isLoading) {
+ return new Array(20)
+ .fill(null)
+ .map((_, index) => );
+ }
+
+ return dataSources.map((dataSource) => (
+
+
+
+ ));
+ };
+
return (
<>
{/* List Header */}
{/* List */}
-
- {dataSources.map((dataSource) => {
- const dsLink = config.appSubUrl + dataSourcesRoutes.Edit.replace(/:uid/gi, dataSource.uid);
- return (
- -
-
- {dataSource.name}
-
-
-
-
- {[
- dataSource.typeName,
- dataSource.url,
- dataSource.isDefault && ,
- ]}
-
-
- {/* Build Dashboard */}
- {
- trackCreateDashboardClicked({
- grafana_version: config.buildInfo.version,
- datasource_uid: dataSource.uid,
- plugin_name: dataSource.typeName,
- path: location.pathname,
- });
- }}
- >
- Build a dashboard
-
-
- {/* Explore */}
- {hasExploreRights && (
- {
- trackExploreClicked({
- grafana_version: config.buildInfo.version,
- datasource_uid: dataSource.uid,
- plugin_name: dataSource.typeName,
- path: location.pathname,
- });
- }}
- >
- Explore
-
- )}
-
-
-
- );
- })}
-
+
>
);
}
@@ -164,11 +116,5 @@ const getStyles = (theme: GrafanaTheme2) => {
display: 'grid',
// gap: '8px', Add back when legacy support for old Card interface is dropped
}),
- logo: css({
- objectFit: 'contain',
- }),
- button: css({
- marginLeft: theme.spacing(2),
- }),
};
};
diff --git a/public/app/features/datasources/components/DataSourcesListCard.tsx b/public/app/features/datasources/components/DataSourcesListCard.tsx
new file mode 100644
index 00000000000..25bbed81d59
--- /dev/null
+++ b/public/app/features/datasources/components/DataSourcesListCard.tsx
@@ -0,0 +1,128 @@
+import { css } from '@emotion/css';
+import React from 'react';
+import Skeleton from 'react-loading-skeleton';
+
+import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
+import { config } from '@grafana/runtime';
+import { Card, LinkButton, Stack, Tag, useStyles2 } from '@grafana/ui';
+
+import { useDataSourcesRoutes } from '../state';
+import { trackCreateDashboardClicked, trackExploreClicked } from '../tracking';
+import { constructDataSourceExploreUrl } from '../utils';
+
+export interface Props {
+ dataSource: DataSourceSettings;
+ hasWriteRights: boolean;
+ hasExploreRights: boolean;
+}
+
+export function DataSourcesListCard({ dataSource, hasWriteRights, hasExploreRights }: Props) {
+ const dataSourcesRoutes = useDataSourcesRoutes();
+ const dsLink = config.appSubUrl + dataSourcesRoutes.Edit.replace(/:uid/gi, dataSource.uid);
+ const styles = useStyles2(getStyles);
+
+ return (
+
+ {dataSource.name}
+
+
+
+
+ {[
+ dataSource.typeName,
+ dataSource.url,
+ dataSource.isDefault && ,
+ ]}
+
+
+ {/* Build Dashboard */}
+ {
+ trackCreateDashboardClicked({
+ grafana_version: config.buildInfo.version,
+ datasource_uid: dataSource.uid,
+ plugin_name: dataSource.typeName,
+ path: location.pathname,
+ });
+ }}
+ >
+ Build a dashboard
+
+
+ {/* Explore */}
+ {hasExploreRights && (
+ {
+ trackExploreClicked({
+ grafana_version: config.buildInfo.version,
+ datasource_uid: dataSource.uid,
+ plugin_name: dataSource.typeName,
+ path: location.pathname,
+ });
+ }}
+ >
+ Explore
+
+ )}
+
+
+ );
+}
+
+function DataSourcesListCardSkeleton({ hasExploreRights }: Pick) {
+ const skeletonStyles = useStyles2(getSkeletonStyles);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Explore */}
+ {hasExploreRights && }
+
+
+
+ );
+}
+
+DataSourcesListCard.Skeleton = DataSourcesListCardSkeleton;
+
+const getSkeletonStyles = () => {
+ return {
+ button: css({
+ lineHeight: 1,
+ }),
+ figure: css({
+ lineHeight: 1,
+ }),
+ };
+};
+
+const getStyles = (theme: GrafanaTheme2) => {
+ return {
+ logo: css({
+ objectFit: 'contain',
+ }),
+ button: css({
+ marginLeft: theme.spacing(2),
+ }),
+ };
+};