mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
5aff3389f4
commit
1efd21c08a
@ -4,16 +4,15 @@ import { useLocation } from 'react-router-dom';
|
|||||||
|
|
||||||
import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
|
import { DataSourceSettings, GrafanaTheme2 } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
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 EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||||
import PageLoader from 'app/core/components/PageLoader/PageLoader';
|
|
||||||
import { contextSrv } from 'app/core/core';
|
import { contextSrv } from 'app/core/core';
|
||||||
import { StoreState, AccessControlAction, useSelector } from 'app/types';
|
import { StoreState, AccessControlAction, useSelector } from 'app/types';
|
||||||
|
|
||||||
import { getDataSources, getDataSourcesCount, useDataSourcesRoutes, useLoadDataSources } from '../state';
|
import { getDataSources, getDataSourcesCount, useDataSourcesRoutes, useLoadDataSources } from '../state';
|
||||||
import { trackCreateDashboardClicked, trackExploreClicked, trackDataSourcesListViewed } from '../tracking';
|
import { trackDataSourcesListViewed } from '../tracking';
|
||||||
import { constructDataSourceExploreUrl } from '../utils';
|
|
||||||
|
|
||||||
|
import { DataSourcesListCard } from './DataSourcesListCard';
|
||||||
import { DataSourcesListHeader } from './DataSourcesListHeader';
|
import { DataSourcesListHeader } from './DataSourcesListHeader';
|
||||||
|
|
||||||
export function DataSourcesList() {
|
export function DataSourcesList() {
|
||||||
@ -65,11 +64,7 @@ export function DataSourcesListView({
|
|||||||
});
|
});
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (!isLoading && dataSourcesCount === 0) {
|
||||||
return <PageLoader />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataSourcesCount === 0) {
|
|
||||||
return (
|
return (
|
||||||
<EmptyListCTA
|
<EmptyListCTA
|
||||||
buttonDisabled={!hasCreateRights}
|
buttonDisabled={!hasCreateRights}
|
||||||
@ -85,74 +80,31 @@ export function DataSourcesListView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDataSourcesList = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return new Array(20)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, index) => <DataSourcesListCard.Skeleton key={index} hasExploreRights={hasExploreRights} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSources.map((dataSource) => (
|
||||||
|
<li key={dataSource.uid}>
|
||||||
|
<DataSourcesListCard
|
||||||
|
dataSource={dataSource}
|
||||||
|
hasWriteRights={hasWriteRights}
|
||||||
|
hasExploreRights={hasExploreRights}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* List Header */}
|
{/* List Header */}
|
||||||
<DataSourcesListHeader />
|
<DataSourcesListHeader />
|
||||||
|
|
||||||
{/* List */}
|
{/* List */}
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>{getDataSourcesList()}</ul>
|
||||||
{dataSources.map((dataSource) => {
|
|
||||||
const dsLink = config.appSubUrl + dataSourcesRoutes.Edit.replace(/:uid/gi, dataSource.uid);
|
|
||||||
return (
|
|
||||||
<li key={dataSource.uid}>
|
|
||||||
<Card href={hasWriteRights ? dsLink : undefined}>
|
|
||||||
<Card.Heading>{dataSource.name}</Card.Heading>
|
|
||||||
<Card.Figure>
|
|
||||||
<img src={dataSource.typeLogoUrl} alt="" height="40px" width="40px" className={styles.logo} />
|
|
||||||
</Card.Figure>
|
|
||||||
<Card.Meta>
|
|
||||||
{[
|
|
||||||
dataSource.typeName,
|
|
||||||
dataSource.url,
|
|
||||||
dataSource.isDefault && <Tag key="default-tag" name={'default'} colorIndex={1} />,
|
|
||||||
]}
|
|
||||||
</Card.Meta>
|
|
||||||
<Card.Tags>
|
|
||||||
{/* Build Dashboard */}
|
|
||||||
<LinkButton
|
|
||||||
icon="apps"
|
|
||||||
fill="outline"
|
|
||||||
variant="secondary"
|
|
||||||
href={`dashboard/new-with-ds/${dataSource.uid}`}
|
|
||||||
onClick={() => {
|
|
||||||
trackCreateDashboardClicked({
|
|
||||||
grafana_version: config.buildInfo.version,
|
|
||||||
datasource_uid: dataSource.uid,
|
|
||||||
plugin_name: dataSource.typeName,
|
|
||||||
path: location.pathname,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Build a dashboard
|
|
||||||
</LinkButton>
|
|
||||||
|
|
||||||
{/* Explore */}
|
|
||||||
{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>
|
|
||||||
)}
|
|
||||||
</Card.Tags>
|
|
||||||
</Card>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -164,11 +116,5 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
display: 'grid',
|
display: 'grid',
|
||||||
// gap: '8px', Add back when legacy support for old Card interface is dropped
|
// gap: '8px', Add back when legacy support for old Card interface is dropped
|
||||||
}),
|
}),
|
||||||
logo: css({
|
|
||||||
objectFit: 'contain',
|
|
||||||
}),
|
|
||||||
button: css({
|
|
||||||
marginLeft: theme.spacing(2),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -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 (
|
||||||
|
<Card href={hasWriteRights ? dsLink : undefined}>
|
||||||
|
<Card.Heading>{dataSource.name}</Card.Heading>
|
||||||
|
<Card.Figure>
|
||||||
|
<img src={dataSource.typeLogoUrl} alt="" height="40px" width="40px" className={styles.logo} />
|
||||||
|
</Card.Figure>
|
||||||
|
<Card.Meta>
|
||||||
|
{[
|
||||||
|
dataSource.typeName,
|
||||||
|
dataSource.url,
|
||||||
|
dataSource.isDefault && <Tag key="default-tag" name={'default'} colorIndex={1} />,
|
||||||
|
]}
|
||||||
|
</Card.Meta>
|
||||||
|
<Card.Tags>
|
||||||
|
{/* Build Dashboard */}
|
||||||
|
<LinkButton
|
||||||
|
icon="apps"
|
||||||
|
fill="outline"
|
||||||
|
variant="secondary"
|
||||||
|
href={`dashboard/new-with-ds/${dataSource.uid}`}
|
||||||
|
onClick={() => {
|
||||||
|
trackCreateDashboardClicked({
|
||||||
|
grafana_version: config.buildInfo.version,
|
||||||
|
datasource_uid: dataSource.uid,
|
||||||
|
plugin_name: dataSource.typeName,
|
||||||
|
path: location.pathname,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Build a dashboard
|
||||||
|
</LinkButton>
|
||||||
|
|
||||||
|
{/* Explore */}
|
||||||
|
{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>
|
||||||
|
)}
|
||||||
|
</Card.Tags>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DataSourcesListCardSkeleton({ hasExploreRights }: Pick<Props, 'hasExploreRights'>) {
|
||||||
|
const skeletonStyles = useStyles2(getSkeletonStyles);
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Card.Heading>
|
||||||
|
<Skeleton width={140} />
|
||||||
|
</Card.Heading>
|
||||||
|
<Card.Figure>
|
||||||
|
<Skeleton width={40} height={40} containerClassName={skeletonStyles.figure} />
|
||||||
|
</Card.Figure>
|
||||||
|
<Card.Meta>
|
||||||
|
<Skeleton width={120} />
|
||||||
|
</Card.Meta>
|
||||||
|
<Card.Tags>
|
||||||
|
<Stack direction="row" gap={2}>
|
||||||
|
<Skeleton height={32} width={179} containerClassName={skeletonStyles.button} />
|
||||||
|
|
||||||
|
{/* Explore */}
|
||||||
|
{hasExploreRights && <Skeleton height={32} width={107} containerClassName={skeletonStyles.button} />}
|
||||||
|
</Stack>
|
||||||
|
</Card.Tags>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user