Skip to content

Commit 868a987

Browse files
committed
OU-1004: List perses dashbaords
1 parent 84085f4 commit 868a987

15 files changed

+546
-192
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.devcontainer/dev.env
22
.DS_Store
3+
.vscode
34
web/cypress/screenshots/
45
web/cypress/export-env.sh
56
web/screenshots/

config/perses-dashboards.patch.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,41 @@
8383
"insertAfter": "dashboards-virt"
8484
}
8585
}
86+
},
87+
{
88+
"op": "add",
89+
"path": "/extensions/1",
90+
"value": {
91+
"type": "console.page/route",
92+
"properties": {
93+
"exact": false,
94+
"path": ["/monitoring/v2/dashboards/view"],
95+
"component": { "$codeRef": "McpDashboardViewerPage" }
96+
}
97+
}
98+
},
99+
{
100+
"op": "add",
101+
"path": "/extensions/1",
102+
"value": {
103+
"type": "console.page/route",
104+
"properties": {
105+
"exact": false,
106+
"path": ["/virt-monitoring/v2/dashboards/view"],
107+
"component": { "$codeRef": "McpDashboardViewerPage" }
108+
}
109+
}
110+
},
111+
{
112+
"op": "add",
113+
"path": "/extensions/1",
114+
"value": {
115+
"type": "console.page/route",
116+
"properties": {
117+
"exact": false,
118+
"path": ["/multicloud/monitoring/v2/dashboards/view"],
119+
"component": { "$codeRef": "McpDashboardViewerPage" }
120+
}
121+
}
86122
}
87123
]

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
"description": "This plugin adds the monitoring UI to the OpenShift web console",
155155
"exposedModules": {
156156
"DashboardsPage": "./components/dashboards/perses/dashboard-page",
157+
"McpDashboardViewerPage": "./components/dashboards/perses/dashboard-viewer-page",
157158
"LegacyDashboardsPage": "./components/dashboards/legacy/legacy-dashboard-page",
158159
"SilencesPage": "./components/alerting/SilencesPage",
159160
"SilencesDetailsPage": "./components/alerting/SilencesDetailsPage",

web/src/components/dashboards/perses/dashoard-app.tsx renamed to web/src/components/dashboards/perses/dashboard-app.tsx

File renamed without changes.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { ReactNode } from 'react';
2+
import { DashboardEmptyState } from './emptystates/DashboardEmptyState';
3+
import { DashboardSkeleton } from './dashboard-skeleton';
4+
import { CombinedDashboardMetadata } from './hooks/useDashboardsData';
5+
import { ProjectBar } from './project/ProjectBar';
6+
import { PersesWrapper } from './PersesWrapper';
7+
import { Overview } from '@openshift-console/dynamic-plugin-sdk';
8+
9+
export interface DashboardLayoutProps {
10+
activeProject: string | null;
11+
setActiveProject: (project: string | null) => void;
12+
activeProjectDashboardsMetadata: CombinedDashboardMetadata[];
13+
changeBoard: (boardName: string) => void;
14+
dashboardName: string;
15+
children: ReactNode;
16+
}
17+
18+
export const DashboardLayout: React.FC<DashboardLayoutProps> = ({
19+
activeProject,
20+
setActiveProject,
21+
activeProjectDashboardsMetadata,
22+
changeBoard,
23+
dashboardName,
24+
children,
25+
}) => {
26+
return (
27+
<>
28+
<ProjectBar activeProject={activeProject} setActiveProject={setActiveProject} />
29+
<PersesWrapper project={activeProject}>
30+
{activeProjectDashboardsMetadata?.length === 0 ? (
31+
<DashboardEmptyState />
32+
) : (
33+
<DashboardSkeleton
34+
boardItems={activeProjectDashboardsMetadata}
35+
changeBoard={changeBoard}
36+
dashboardName={dashboardName}
37+
activeProject={activeProject}
38+
>
39+
<Overview>{children}</Overview>
40+
</DashboardSkeleton>
41+
)}
42+
</PersesWrapper>
43+
</>
44+
);
45+
};
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import React, { ReactNode, useMemo, type FC } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import { DashboardLayout } from './dashboard-layout';
4+
import { useDashboardsData } from './hooks/useDashboardsData';
5+
6+
import { Pagination } from '@patternfly/react-core';
7+
import { DataView } from '@patternfly/react-data-view/dist/dynamic/DataView';
8+
import { DataViewFilters } from '@patternfly/react-data-view/dist/dynamic/DataViewFilters';
9+
import {
10+
DataViewTable,
11+
DataViewTh,
12+
DataViewTr,
13+
} from '@patternfly/react-data-view/dist/dynamic/DataViewTable';
14+
import { DataViewTextFilter } from '@patternfly/react-data-view/dist/dynamic/DataViewTextFilter';
15+
import { DataViewToolbar } from '@patternfly/react-data-view/dist/dynamic/DataViewToolbar';
16+
import { useDataViewFilters, useDataViewSort } from '@patternfly/react-data-view';
17+
import { useDataViewPagination } from '@patternfly/react-data-view/dist/dynamic/Hooks';
18+
import { ThProps } from '@patternfly/react-table';
19+
import { Link, useSearchParams } from 'react-router-dom-v5-compat';
20+
21+
import { getDashboardUrl, usePerspective } from '../../hooks/usePerspective';
22+
23+
const perPageOptions = [
24+
{ title: '10', value: 10 },
25+
{ title: '20', value: 20 },
26+
];
27+
28+
interface DashboardName {
29+
link: ReactNode;
30+
label: string;
31+
}
32+
33+
interface DashboardRow {
34+
name: DashboardName;
35+
project: string;
36+
created: string;
37+
modified: string;
38+
}
39+
40+
interface DashboardRowFilters {
41+
name?: string;
42+
'project-filter'?: string;
43+
}
44+
45+
const DASHBOARD_COLUMNS = [
46+
{ label: 'Dashboard', key: 'name' as keyof DashboardRow, index: 0 },
47+
{ label: 'Project', key: 'project' as keyof DashboardRow, index: 1 },
48+
{ label: 'Created on', key: 'created' as keyof DashboardRow, index: 2 },
49+
{ label: 'Last Modified', key: 'modified' as keyof DashboardRow, index: 3 },
50+
];
51+
52+
const sortDashboardData = (
53+
data: DashboardRow[],
54+
sortBy: keyof DashboardRow | undefined,
55+
direction: 'asc' | 'desc' | undefined,
56+
): DashboardRow[] =>
57+
sortBy && direction
58+
? [...data].sort((a, b) => {
59+
const aValue = sortBy === 'name' ? a.name.label : a[sortBy];
60+
const bValue = sortBy === 'name' ? b.name.label : b[sortBy];
61+
62+
if (direction === 'asc') {
63+
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
64+
} else {
65+
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
66+
}
67+
})
68+
: data;
69+
70+
interface DashboardsTableProps {
71+
persesDashboards: Array<{
72+
metadata?: {
73+
name?: string;
74+
project?: string;
75+
createdAt?: string;
76+
updatedAt?: string;
77+
};
78+
}>;
79+
persesDashboardsLoading: boolean;
80+
}
81+
82+
const DashboardsTable: React.FunctionComponent<DashboardsTableProps> = ({
83+
persesDashboards,
84+
persesDashboardsLoading,
85+
}) => {
86+
const { t } = useTranslation(process.env.I18N_NAMESPACE);
87+
88+
const { perspective } = usePerspective();
89+
const dashboardBaseURL = getDashboardUrl(perspective);
90+
91+
const [searchParams, setSearchParams] = useSearchParams();
92+
const { sortBy, direction, onSort } = useDataViewSort({ searchParams, setSearchParams });
93+
94+
const { filters, onSetFilters, clearAllFilters } = useDataViewFilters<DashboardRowFilters>({
95+
initialFilters: { name: '', 'project-filter': '' },
96+
searchParams,
97+
setSearchParams,
98+
});
99+
const pagination = useDataViewPagination({ perPage: perPageOptions[0].value });
100+
const { page, perPage } = pagination;
101+
102+
const sortByIndex = useMemo(
103+
() => DASHBOARD_COLUMNS.findIndex((item) => item.key === sortBy),
104+
[sortBy],
105+
);
106+
107+
const getSortParams = (columnIndex: number): ThProps['sort'] => ({
108+
sortBy: {
109+
index: sortByIndex,
110+
direction,
111+
defaultDirection: 'asc',
112+
},
113+
onSort: (_event, index, direction) => onSort(_event, DASHBOARD_COLUMNS[index].key, direction),
114+
columnIndex,
115+
});
116+
117+
const tableColumns: DataViewTh[] = DASHBOARD_COLUMNS.map((column, index) => ({
118+
cell: t(column.label),
119+
props: { sort: getSortParams(index) },
120+
}));
121+
122+
const tableRows: DashboardRow[] = useMemo(() => {
123+
if (persesDashboardsLoading) {
124+
return [];
125+
}
126+
return persesDashboards.map((board) => {
127+
const metadata = board?.metadata;
128+
const dashboardsParams = `?dashboard=${metadata?.name}&project=${metadata?.project}`;
129+
const dashboardName: DashboardName = {
130+
link: (
131+
<Link
132+
to={`${dashboardBaseURL}${dashboardsParams}`}
133+
data-test={`perseslistpage-${board?.metadata?.name}`}
134+
>
135+
{metadata?.name}
136+
</Link>
137+
),
138+
label: metadata?.name || '',
139+
};
140+
141+
return {
142+
name: dashboardName,
143+
project: board?.metadata?.project || '',
144+
created: board?.metadata?.createdAt || '',
145+
modified: board?.metadata?.updatedAt || '',
146+
};
147+
});
148+
}, [dashboardBaseURL, persesDashboards, persesDashboardsLoading]);
149+
150+
const filteredData = useMemo(
151+
() =>
152+
tableRows.filter(
153+
(item) =>
154+
(!filters.name ||
155+
item.name?.label?.toLocaleLowerCase().includes(filters.name?.toLocaleLowerCase())) &&
156+
(!filters['project-filter'] ||
157+
item.project
158+
?.toLocaleLowerCase()
159+
.includes(filters['project-filter']?.toLocaleLowerCase())),
160+
),
161+
[filters, tableRows],
162+
);
163+
164+
const sortedAndFilteredData = useMemo(
165+
() => sortDashboardData(filteredData, sortBy as keyof DashboardRow, direction),
166+
[filteredData, sortBy, direction],
167+
);
168+
169+
const pageRows: DataViewTr[] = useMemo(
170+
() =>
171+
sortedAndFilteredData
172+
.slice((page - 1) * perPage, (page - 1) * perPage + perPage)
173+
.map(({ name, project, created, modified }) => [name.link, project, created, modified]),
174+
[page, perPage, sortedAndFilteredData],
175+
);
176+
177+
const PaginationTool = () => {
178+
return (
179+
<Pagination
180+
perPageOptions={perPageOptions}
181+
itemCount={sortedAndFilteredData.length}
182+
{...pagination}
183+
/>
184+
);
185+
};
186+
187+
return (
188+
<DataView>
189+
<DataViewToolbar
190+
ouiaId="PersesDashList-DataViewHeader"
191+
clearAllFilters={clearAllFilters}
192+
pagination={<PaginationTool />}
193+
filters={
194+
<DataViewFilters onChange={(_e, values) => onSetFilters(values)} values={filters}>
195+
<DataViewTextFilter filterId="name" title="Name" placeholder="Filter by name" />
196+
<DataViewTextFilter
197+
filterId="project-filter"
198+
title="Project"
199+
placeholder="Filter by project"
200+
/>
201+
</DataViewFilters>
202+
}
203+
/>
204+
<DataViewTable
205+
aria-label="Perses Dashboards List"
206+
ouiaId={'PersesDashList-DataViewTable'}
207+
columns={tableColumns}
208+
rows={pageRows}
209+
/>
210+
<DataViewToolbar ouiaId="PersesDashList-DataViewFooter" pagination={<PaginationTool />} />
211+
</DataView>
212+
);
213+
};
214+
215+
export const DashboardListPage: FC = () => {
216+
const {
217+
activeProjectDashboardsMetadata,
218+
changeBoard,
219+
dashboardName,
220+
setActiveProject,
221+
activeProject,
222+
persesDashboards,
223+
combinedInitialLoad,
224+
} = useDashboardsData();
225+
226+
return (
227+
<DashboardLayout
228+
activeProject={activeProject}
229+
setActiveProject={setActiveProject}
230+
activeProjectDashboardsMetadata={activeProjectDashboardsMetadata}
231+
changeBoard={changeBoard}
232+
dashboardName={dashboardName}
233+
>
234+
<DashboardsTable
235+
persesDashboards={persesDashboards}
236+
persesDashboardsLoading={combinedInitialLoad}
237+
/>
238+
</DashboardLayout>
239+
);
240+
};

0 commit comments

Comments
 (0)