mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Signed-off-by: bergquist <carl.bergquist@gmail.com> Co-authored-by: Bogdan Matei <bogdan.matei@grafana.com>
149 lines
4.4 KiB
TypeScript
149 lines
4.4 KiB
TypeScript
import { css } from '@emotion/css';
|
|
import React from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
import { AppEvents, GrafanaTheme2, ScopeDashboard } from '@grafana/data';
|
|
import { config, getAppEvents, getBackendSrv, locationService } from '@grafana/runtime';
|
|
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
|
import { CustomScrollbar, Icon, Input, useStyles2 } from '@grafana/ui';
|
|
|
|
export interface ScopesDashboardsSceneState extends SceneObjectState {
|
|
dashboards: ScopeDashboard[];
|
|
filteredDashboards: ScopeDashboard[];
|
|
isLoading: boolean;
|
|
searchQuery: string;
|
|
}
|
|
|
|
export class ScopesDashboardsScene extends SceneObjectBase<ScopesDashboardsSceneState> {
|
|
static Component = ScopesDashboardsSceneRenderer;
|
|
|
|
private _url =
|
|
config.bootData.settings.listDashboardScopesEndpoint || '/apis/scope.grafana.app/v0alpha1/scopedashboards';
|
|
|
|
constructor() {
|
|
super({
|
|
dashboards: [],
|
|
filteredDashboards: [],
|
|
isLoading: false,
|
|
searchQuery: '',
|
|
});
|
|
}
|
|
|
|
public async fetchDashboards(scope: string | undefined) {
|
|
if (!scope) {
|
|
return this.setState({ dashboards: [], filteredDashboards: [], isLoading: false });
|
|
}
|
|
|
|
this.setState({ isLoading: true });
|
|
|
|
const dashboardUids = await this.fetchDashboardsUids(scope);
|
|
const dashboards = await this.fetchDashboardsDetails(dashboardUids);
|
|
|
|
this.setState({
|
|
dashboards,
|
|
filteredDashboards: this.filterDashboards(dashboards, this.state.searchQuery),
|
|
isLoading: false,
|
|
});
|
|
}
|
|
|
|
public changeSearchQuery(searchQuery: string) {
|
|
this.setState({
|
|
filteredDashboards: searchQuery
|
|
? this.filterDashboards(this.state.dashboards, searchQuery)
|
|
: this.state.dashboards,
|
|
searchQuery: searchQuery ?? '',
|
|
});
|
|
}
|
|
|
|
private async fetchDashboardsUids(scope: string): Promise<string[]> {
|
|
try {
|
|
const response = await getBackendSrv().get<{
|
|
items: Array<{ spec: { dashboards: null | string[]; scope: string } }>;
|
|
}>(this._url, { scope });
|
|
|
|
return response.items.find((item) => !!item.spec.dashboards && item.spec.scope === scope)?.spec.dashboards ?? [];
|
|
} catch (err) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private async fetchDashboardsDetails(dashboardUids: string[]): Promise<ScopeDashboard[]> {
|
|
try {
|
|
const dashboards = await Promise.all(
|
|
dashboardUids.map((dashboardUid) => this.fetchDashboardDetails(dashboardUid))
|
|
);
|
|
|
|
return dashboards.filter((dashboard): dashboard is ScopeDashboard => !!dashboard);
|
|
} catch (err) {
|
|
getAppEvents().publish({
|
|
type: AppEvents.alertError.name,
|
|
payload: ['Failed to fetch suggested dashboards'],
|
|
});
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private async fetchDashboardDetails(dashboardUid: string): Promise<ScopeDashboard | undefined> {
|
|
try {
|
|
const dashboard = await getBackendSrv().get(`/api/dashboards/uid/${dashboardUid}`);
|
|
|
|
return {
|
|
uid: dashboard.dashboard.uid,
|
|
title: dashboard.dashboard.title,
|
|
url: dashboard.meta.url,
|
|
};
|
|
} catch (err) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
private filterDashboards(dashboards: ScopeDashboard[], searchQuery: string) {
|
|
const lowerCasedSearchQuery = searchQuery.toLowerCase();
|
|
return dashboards.filter((dashboard) => dashboard.title.toLowerCase().includes(lowerCasedSearchQuery));
|
|
}
|
|
}
|
|
|
|
export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps<ScopesDashboardsScene>) {
|
|
const { filteredDashboards, isLoading } = model.useState();
|
|
const styles = useStyles2(getStyles);
|
|
|
|
return (
|
|
<>
|
|
<div className={styles.searchInputContainer}>
|
|
<Input
|
|
prefix={<Icon name="search" />}
|
|
disabled={isLoading}
|
|
onChange={(evt) => model.changeSearchQuery(evt.currentTarget.value)}
|
|
/>
|
|
</div>
|
|
|
|
<CustomScrollbar>
|
|
{filteredDashboards.map((dashboard, idx) => (
|
|
<div key={idx} className={styles.dashboardItem}>
|
|
<Link to={{ pathname: dashboard.url, search: locationService.getLocation().search }}>
|
|
{dashboard.title}
|
|
</Link>
|
|
</div>
|
|
))}
|
|
</CustomScrollbar>
|
|
</>
|
|
);
|
|
}
|
|
|
|
const getStyles = (theme: GrafanaTheme2) => {
|
|
return {
|
|
searchInputContainer: css({
|
|
flex: '0 1 auto',
|
|
}),
|
|
dashboardItem: css({
|
|
padding: theme.spacing(1, 0),
|
|
borderBottom: `1px solid ${theme.colors.border.weak}`,
|
|
|
|
':first-child': {
|
|
paddingTop: 0,
|
|
},
|
|
}),
|
|
};
|
|
};
|