Skip to content

Commit

Permalink
Merge pull request #1742 from rawagner/dashboards-page
Browse files Browse the repository at this point in the history
Add plugin infra for adding new dashboard tabs/cards
  • Loading branch information
openshift-merge-robot committed Jun 26, 2019
2 parents 4f68117 + e29751c commit 3034dac
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 29 deletions.
18 changes: 18 additions & 0 deletions frontend/packages/console-demo-plugin/src/dashboards/foo-card.tsx
@@ -0,0 +1,18 @@
import * as React from 'react';
import {
DashboardCard,
DashboardCardTitle,
DashboardCardBody,
DashboardCardHeader,
} from '@console/internal/components/dashboard/dashboard-card';

export const FooCard: React.FC<{}> = () => (
<DashboardCard>
<DashboardCardHeader>
<DashboardCardTitle>Foo Card</DashboardCardTitle>
</DashboardCardHeader>
<DashboardCardBody>
<div>foo content</div>
</DashboardCardBody>
</DashboardCard>
);
23 changes: 22 additions & 1 deletion frontend/packages/console-demo-plugin/src/plugin.tsx
Expand Up @@ -14,11 +14,14 @@ import {
RoutePage,
DashboardsOverviewHealthPrometheusSubsystem,
DashboardsOverviewHealthURLSubsystem,
DashboardsCard,
DashboardsTab,
} from '@console/plugin-sdk';

// TODO(vojtech): internal code needed by plugins should be moved to console-shared package
import { PodModel } from '@console/internal/models';
import { FLAGS } from '@console/internal/const';
import { GridPosition } from '@console/internal/components/dashboard/grid';

import { FooBarModel } from './models';
import { yamlTemplates } from './yaml-templates';
Expand All @@ -37,7 +40,9 @@ type ConsumedExtensions =
| YAMLTemplate
| RoutePage
| DashboardsOverviewHealthPrometheusSubsystem
| DashboardsOverviewHealthURLSubsystem<any>;
| DashboardsOverviewHealthURLSubsystem<any>
| DashboardsTab
| DashboardsCard;

const plugin: Plugin<ConsumedExtensions> = [
{
Expand Down Expand Up @@ -167,6 +172,22 @@ const plugin: Plugin<ConsumedExtensions> = [
render: () => <h1>Test Page</h1>,
},
},
{
type: 'Dashboards/Tab',
properties: {
id: 'foo-tab',
title: 'Foo',
},
},
{
type: 'Dashboards/Card',
properties: {
tab: 'foo-tab',
position: GridPosition.MAIN,
loader: () =>
import('./dashboards/foo-card' /* webpackChunkName: "demo-card" */).then((m) => m.FooCard),
},
},
];

export default plugin;
10 changes: 10 additions & 0 deletions frontend/packages/console-plugin-sdk/src/registry.ts
Expand Up @@ -12,6 +12,8 @@ import {
isYAMLTemplate,
isRoutePage,
isDashboardsOverviewHealthSubsystem,
isDashboardsCard,
isDashboardsTab,
} from './typings';

/**
Expand Down Expand Up @@ -59,4 +61,12 @@ export class ExtensionRegistry {
public getDashboardsOverviewHealthSubsystems() {
return this.extensions.filter(isDashboardsOverviewHealthSubsystem);
}

public getDashboardsTabs() {
return this.extensions.filter(isDashboardsTab);
}

public getDashboardsCards() {
return this.extensions.filter(isDashboardsCard);
}
}
36 changes: 36 additions & 0 deletions frontend/packages/console-plugin-sdk/src/typings/dashboards.ts
@@ -1,5 +1,8 @@
import { SubsystemHealth } from '@console/internal/components/dashboards-page/overview-dashboard/health-card';
import { GridPosition } from '@console/internal/components/dashboard/grid';

import { Extension } from './extension';
import { LazyLoader } from './types';

namespace ExtensionProperties {
interface DashboardsOverviewHealthSubsystem<R> {
Expand Down Expand Up @@ -31,6 +34,25 @@ namespace ExtensionProperties {
/** The Prometheus query */
query: string;
}

export interface DashboardsTab {
/** The tab's ID which will be used as part of href within dashboards page */
id: string;

/** The tab title */
title: string;
}

export interface DashboardsCard {
/** The tab's ID where this card should be rendered */
tab: string;

/** The card position in the tab */
position: GridPosition;

/** Loader for the corresponding dashboard card component. */
loader: LazyLoader<any>;
}
}

export interface DashboardsOverviewHealthURLSubsystem<R>
Expand Down Expand Up @@ -60,3 +82,17 @@ export const isDashboardsOverviewHealthSubsystem = (
e: Extension<any>,
): e is DashboardsOverviewHealthSubsystem =>
isDashboardsOverviewHealthURLSubsystem(e) || isDashboardsOverviewHealthPrometheusSubsystem(e);

export interface DashboardsTab extends Extension<ExtensionProperties.DashboardsTab> {
type: 'Dashboards/Tab';
}

export const isDashboardsTab = (e: Extension<any>): e is DashboardsTab =>
e.type === 'Dashboards/Tab';

export interface DashboardsCard extends Extension<ExtensionProperties.DashboardsCard> {
type: 'Dashboards/Card';
}

export const isDashboardsCard = (e: Extension<any>): e is DashboardsCard =>
e.type === 'Dashboards/Card';
4 changes: 1 addition & 3 deletions frontend/packages/console-plugin-sdk/src/typings/pages.ts
@@ -1,9 +1,7 @@
import * as React from 'react';
import { RouteProps, RouteComponentProps } from 'react-router-dom';
import { K8sKind, K8sResourceKindReference } from '@console/internal/module/k8s';
import { Extension } from './extension';

type LazyLoader<T extends {}> = () => Promise<React.ComponentType<Partial<T>>>;
import { LazyLoader } from './types';

namespace ExtensionProperties {
export interface ResourcePage<T> {
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/console-plugin-sdk/src/typings/types.ts
@@ -0,0 +1 @@
export type LazyLoader<T extends {}> = () => Promise<React.ComponentType<Partial<T>>>;
2 changes: 1 addition & 1 deletion frontend/public/components/app-contents.tsx
Expand Up @@ -118,7 +118,7 @@ const AppContents = withRouter(React.memo(() => (

<Route path={['/all-namespaces', '/ns/:ns']} component={RedirectComponent} />

<LazyRoute path="/dashboards" exact loader={() => import('./dashboards-page/dashboards' /* webpackChunkName: "dashboards" */).then(m => m.DashboardsPage)} />
<LazyRoute path="/dashboards" loader={() => import('./dashboards-page/dashboards' /* webpackChunkName: "dashboards" */).then(m => m.DashboardsPage)} />
<LazyRoute path="/cluster-status" exact loader={() => import('./cluster-overview' /* webpackChunkName: "cluster-overview" */).then(m => m.ClusterOverviewPage)} />
<Redirect from="/overview/all-namespaces" to="/cluster-status" />
<Redirect from="/overview/ns/:ns" to="/k8s/cluster/projects/:ns/workloads" />
Expand Down
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import classNames from 'classnames';
import { CardBody, CardBodyProps } from '@patternfly/react-core';

import { LoadingInline } from '../../utils';
import { LoadingInline } from '../../utils/status-box';

export const DashboardCardBody: React.FC<DashboardCardBodyProps> = React.memo(({ isLoading, classname, children, ...props }) => (
<CardBody className={classNames('co-dashboard-card__body', classname)} {...props}>
Expand Down
45 changes: 34 additions & 11 deletions frontend/public/components/dashboard/grid.tsx
@@ -1,35 +1,58 @@
import * as React from 'react';
import { Grid, GridItem } from '@patternfly/react-core';

import { useRefWidth } from '../utils';
import { useRefWidth } from '../utils/ref-width-hook';

export const MEDIA_QUERY_LG = 992;

export const DashboardGrid: React.FC<DashboardGridProps> = ({ mainCards, leftCards, rightCards }) => {
export enum GridPosition {
MAIN = 'MAIN',
LEFT = 'LEFT',
RIGHT = 'RIGHT',
}

const mapCardsToGrid = (cards: React.ComponentType<any>[], keyPrefix: string): React.ReactNode[] =>
cards.map((Card, index) => (
<GridItem key={`${keyPrefix}-${index}`} span={12}><Card /></GridItem>
));

export const DashboardGrid: React.FC<DashboardGridProps> = ({ mainCards, leftCards = [], rightCards = [] }) => {
const [containerRef, width] = useRefWidth();
const grid = width <= MEDIA_QUERY_LG ?
(
<Grid className="co-dashboard-grid">
<GridItem lg={12} md={12} sm={12}>
{mainCards}
<Grid className="co-dashboard-grid">
{mapCardsToGrid(mainCards, 'main')}
</Grid>
</GridItem>
<GridItem key="left" lg={12} md={12} sm={12}>
{leftCards}
<Grid className="co-dashboard-grid">
{mapCardsToGrid(leftCards, 'left')}
</Grid>
</GridItem>
<GridItem key="right" lg={12} md={12} sm={12}>
{rightCards}
<Grid className="co-dashboard-grid">
{mapCardsToGrid(rightCards, 'right')}
</Grid>
</GridItem>
</Grid>
) : (
<Grid className="co-dashboard-grid">
<GridItem key="left" lg={3} md={3} sm={3}>
{leftCards}
<Grid className="co-dashboard-grid">
{mapCardsToGrid(leftCards, 'left')}
</Grid>
</GridItem>
<GridItem lg={6} md={6} sm={6}>
{mainCards}
<Grid className="co-dashboard-grid">
{mapCardsToGrid(mainCards, 'main')}
</Grid>
</GridItem>
<GridItem key="right" lg={3} md={3} sm={3}>
{rightCards}
<Grid className="co-dashboard-grid">
{mapCardsToGrid(rightCards, 'right')}
</Grid>
</GridItem>
</Grid>
);
Expand All @@ -38,7 +61,7 @@ export const DashboardGrid: React.FC<DashboardGridProps> = ({ mainCards, leftCar
};

type DashboardGridProps = {
mainCards: React.ReactNode,
leftCards?: React.ReactNode,
rightCards?: React.ReactNode,
mainCards: React.ComponentType<any>[],
leftCards?: React.ComponentType<any>[],
rightCards?: React.ComponentType<any>[],
};
36 changes: 32 additions & 4 deletions frontend/public/components/dashboards-page/dashboards.tsx
Expand Up @@ -2,18 +2,46 @@ import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { connect } from 'react-redux';

import * as plugins from '../../plugins';
import { OverviewDashboard } from './overview-dashboard/overview-dashboard';
import { HorizontalNav, PageHeading, LoadingBox } from '../utils';
import { HorizontalNav, PageHeading, LoadingBox, Page, AsyncComponent } from '../utils';
import { Dashboard } from '../dashboard/dashboard';
import { DashboardGrid, GridPosition } from '../dashboard/grid';
import { DashboardsCard } from '@console/plugin-sdk';

const tabs = [
const getCardsOnPosition = (cards: DashboardsCard[], position: GridPosition): React.ComponentType<any>[] =>
cards.filter(c => c.properties.position === position).map(c => () => <AsyncComponent loader={c.properties.loader} />);

const getPluginTabPages = (): Page[] => {
const cards = plugins.registry.getDashboardsCards();
return plugins.registry.getDashboardsTabs().map(tab => {
const tabCards = cards.filter(c => c.properties.tab === tab.properties.id);
return {
href: tab.properties.id,
name: tab.properties.title,
component: () => (
<Dashboard>
<DashboardGrid
mainCards={getCardsOnPosition(tabCards, GridPosition.MAIN)}
leftCards={getCardsOnPosition(tabCards, GridPosition.LEFT)}
rightCards={getCardsOnPosition(tabCards, GridPosition.RIGHT)}
/>
</Dashboard>
),
};
});
};

const tabs: Page[] = [
{
href: '',
name: 'Overview',
component: OverviewDashboard,
},
...getPluginTabPages(),
];

const _DashboardsPage: React.FC<DashboardsPageProps> = ({ match, kindsInFlight }) => {
const DashboardsPage_: React.FC<DashboardsPageProps> = ({ match, kindsInFlight }) => {
return kindsInFlight
? <LoadingBox />
: (
Expand All @@ -28,7 +56,7 @@ const mapStateToProps = ({k8s}) => ({
kindsInFlight: k8s.getIn(['RESOURCES', 'inFlight']),
});

export const DashboardsPage = connect(mapStateToProps)(_DashboardsPage);
export const DashboardsPage = connect(mapStateToProps)(DashboardsPage_);

type DashboardsPageProps = RouteComponentProps & {
kindsInFlight: boolean;
Expand Down
Expand Up @@ -5,13 +5,8 @@ import { HealthCard } from './health-card';
import { DetailsCard } from './details-card';

export const OverviewDashboard: React.FC<{}> = () => {
const mainCards = [
<HealthCard key="health" />,
];

const leftCards = [
<DetailsCard key="details" />,
];
const mainCards = [HealthCard];
const leftCards = [DetailsCard];

return (
<Dashboard>
Expand Down
2 changes: 1 addition & 1 deletion frontend/public/components/utils/horizontal-nav.tsx
Expand Up @@ -26,7 +26,7 @@ class PodsComponent extends React.PureComponent<PodsComponentProps> {
}
}

type Page = {
export type Page = {
href: string;
name: string;
component?: React.ComponentType<any>;
Expand Down

0 comments on commit 3034dac

Please sign in to comment.