Files
grafana/public/app/features/dashboard-scene/scene/ScopesDashboardsScene.tsx
Carl Bergquist b214b07695 Scopes: Name relationship objects *binding (#84955)
Signed-off-by: bergquist <carl.bergquist@gmail.com>
Co-authored-by: Bogdan Matei <bogdan.matei@grafana.com>
2024-03-26 15:52:12 +01:00

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,
},
}),
};
};