From 1efd21c08abaa57ff30e5322cd6292b0d0f83f39 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 13 Dec 2023 09:50:27 +0000 Subject: [PATCH] Data sources: Add skeleton loader (#79016) * refactor out DataSourcesListCard * add skeleton * increase gap between buttons on skeleton to match real component * lineHeight: 1 instead of 0 * refactor out ternary --- .../components/DataSourcesList.tsx | 100 ++++---------- .../components/DataSourcesListCard.tsx | 128 ++++++++++++++++++ 2 files changed, 151 insertions(+), 77 deletions(-) create mode 100644 public/app/features/datasources/components/DataSourcesListCard.tsx 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 - - )} - - -
    • - ); - })} -
    +
      {getDataSourcesList()}
    ); } @@ -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), + }), + }; +};