Skip to content

Commit

Permalink
Merge pull request #13240 from TheRealJon/CONSOLE-3695
Browse files Browse the repository at this point in the history
CONSOLE-3695: Add details item extension to console dynamic plugin SDK
  • Loading branch information
openshift-merge-bot[bot] committed Nov 10, 2023
2 parents da9f5f5 + 8a25a60 commit 33225d2
Show file tree
Hide file tree
Showing 15 changed files with 289 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ export const ConsoleOperatorConfigDetailsPage: React.FC<React.ComponentProps<
typeof DetailsPage
>> = (props) => {
const pages = [
navFactory.details(DetailsForKind(props.kind)),
navFactory.details(DetailsForKind),
navFactory.editYaml(),
{
href: 'console-plugins',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,33 @@
49. [console.pvc/status](#consolepvcstatus)
50. [console.redux-reducer](#consoleredux-reducer)
51. [console.resource/create](#consoleresourcecreate)
52. [console.storage-class/provisioner](#consolestorage-classprovisioner)
53. [console.storage-provider](#consolestorage-provider)
54. [console.tab](#consoletab)
55. [console.tab/horizontalNav](#consoletabhorizontalNav)
56. [console.telemetry/listener](#consoletelemetrylistener)
57. [console.topology/adapter/build](#consoletopologyadapterbuild)
58. [console.topology/adapter/network](#consoletopologyadapternetwork)
59. [console.topology/adapter/pod](#consoletopologyadapterpod)
60. [console.topology/component/factory](#consoletopologycomponentfactory)
61. [console.topology/create/connector](#consoletopologycreateconnector)
62. [console.topology/data/factory](#consoletopologydatafactory)
63. [console.topology/decorator/provider](#consoletopologydecoratorprovider)
64. [console.topology/details/resource-alert](#consoletopologydetailsresource-alert)
65. [console.topology/details/resource-link](#consoletopologydetailsresource-link)
66. [console.topology/details/tab](#consoletopologydetailstab)
67. [console.topology/details/tab-section](#consoletopologydetailstab-section)
68. [console.topology/display/filters](#consoletopologydisplayfilters)
69. [console.topology/relationship/provider](#consoletopologyrelationshipprovider)
70. [console.user-preference/group](#consoleuser-preferencegroup)
71. [console.user-preference/item](#consoleuser-preferenceitem)
72. [console.yaml-template](#consoleyaml-template)
73. [dev-console.add/action](#dev-consoleaddaction)
74. [dev-console.add/action-group](#dev-consoleaddaction-group)
75. [dev-console.import/environment](#dev-consoleimportenvironment)
76. [DEPRECATED] [console.dashboards/overview/detail/item](#consoledashboardsoverviewdetailitem)
77. [DEPRECATED] [console.page/resource/tab](#consolepageresourcetab)
52. [console.resource/details-item](#consoleresourcedetails-item)
53. [console.storage-class/provisioner](#consolestorage-classprovisioner)
54. [console.storage-provider](#consolestorage-provider)
55. [console.tab](#consoletab)
56. [console.tab/horizontalNav](#consoletabhorizontalNav)
57. [console.telemetry/listener](#consoletelemetrylistener)
58. [console.topology/adapter/build](#consoletopologyadapterbuild)
59. [console.topology/adapter/network](#consoletopologyadapternetwork)
60. [console.topology/adapter/pod](#consoletopologyadapterpod)
61. [console.topology/component/factory](#consoletopologycomponentfactory)
62. [console.topology/create/connector](#consoletopologycreateconnector)
63. [console.topology/data/factory](#consoletopologydatafactory)
64. [console.topology/decorator/provider](#consoletopologydecoratorprovider)
65. [console.topology/details/resource-alert](#consoletopologydetailsresource-alert)
66. [console.topology/details/resource-link](#consoletopologydetailsresource-link)
67. [console.topology/details/tab](#consoletopologydetailstab)
68. [console.topology/details/tab-section](#consoletopologydetailstab-section)
69. [console.topology/display/filters](#consoletopologydisplayfilters)
70. [console.topology/relationship/provider](#consoletopologyrelationshipprovider)
71. [console.user-preference/group](#consoleuser-preferencegroup)
72. [console.user-preference/item](#consoleuser-preferenceitem)
73. [console.yaml-template](#consoleyaml-template)
74. [dev-console.add/action](#dev-consoleaddaction)
75. [dev-console.add/action-group](#dev-consoleaddaction-group)
76. [dev-console.import/environment](#dev-consoleimportenvironment)
77. [DEPRECATED] [console.dashboards/overview/detail/item](#consoledashboardsoverviewdetailitem)
78. [DEPRECATED] [console.page/resource/tab](#consolepageresourcetab)

---

Expand Down Expand Up @@ -943,6 +944,26 @@ This extension allows plugins to provide a custom component (ie wizard or form)

---

## `console.resource/details-item`

### Summary

Adds a new details item to the default resource summary on the details page.

### Properties

| Name | Value Type | Optional | Description |
| ---- | ---------- | -------- | ----------- |
| `model` | `ExtensionK8sModel` | no | The subject resource's API group, version, and kind. |
| `id` | `string` | no | A unique identifier. |
| `column` | `DetailsItemColumn` | no | Determines if the item will appear in the 'left' or 'right' column of the resource summary on<br/>the details page. Default: 'right' |
| `title` | `string` | no | The details item title. |
| `path` | `string` | yes | An optional, fully-qualified path to a resource property to used as the details item<br/>value. Only [primitive type](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)<br/>values can be rendered directly. Use the component property to handle other data types. |
| `component` | `CodeRef<React.ComponentType<DetailsItemComponentProps<K8sResourceCommon, any>>>` | yes | An optional React component that will render the details item value. |
| `sortWeight` | `number` | yes | An optional sort weight, relative to all other details items in the same column. Represented<br/>by any valid [JavaScript<br/>Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type).<br/>Items in each column are sorted independently, lowest to highest. Items without sort weights<br/>are sorted after items with sort weights. |

---

## `console.storage-class/provisioner`

### Summary
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ExtensionK8sModel } from '../api/common-types';
import { Extension, ExtensionDeclaration, CodeRef } from '../types';
import { K8sResourceCommon } from './console-types';

export type DetailsItemColumn = 'right' | 'left';

export type DetailsItemComponentProps<R extends K8sResourceCommon = K8sResourceCommon, V = any> = {
/** The subject resource */
obj: R;

/** The path property provided by the extension */
path?: string;

/** The property of obj referenced by path */
value?: V;
};

/** Adds a new details item to the default resource summary on the details page. */
export type DetailsItem = ExtensionDeclaration<
'console.resource/details-item',
{
/** The subject resource's API group, version, and kind. */
model: ExtensionK8sModel;

/** A unique identifier. */
id: string;

/**
* Determines if the item will appear in the 'left' or 'right' column of the resource summary on
* the details page. Default: 'right'
*/
column: DetailsItemColumn;

/** The details item title. */
title: string;

/**
* An optional, fully-qualified path to a resource property to used as the details item
* value. Only [primitive type](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)
* values can be rendered directly. Use the component property to handle other data types.
*/
path?: string;

/**
* An optional React component that will render the details item value.
*/
component?: CodeRef<React.ComponentType<DetailsItemComponentProps>>;

/**
* An optional sort weight, relative to all other details items in the same column. Represented
* by any valid [JavaScript
* Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type).
* Items in each column are sorted independently, lowest to highest. Items without sort weights
* are sorted after items with sort weights.
*/
sortWeight?: number;
}
>;

export const isDetailsItem = (e: Extension): e is DetailsItem =>
e.type === 'console.resource/details-item';

export const isLeftDetailsItem = (e: DetailsItem): e is DetailsItem =>
isDetailsItem(e) && e.properties.column === 'left';

export const isRightDetailsItem = (e: DetailsItem): e is DetailsItem =>
isDetailsItem(e) && e.properties.column === 'right';
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
export * from './actions';
export * from './add-actions';
export * from './alerts';
export * from './breadcrumbs';
export * from './catalog';
export * from './cluster-configuration';
export * from './cluster-overview';
export * from './cluster-settings';
export * from './console-types';
export * from './context-providers';
export * from './create-resource';
export * from './dashboard-types';
export * from './dashboards';
export * from './details-item';
export * from './feature-flags';
export * from './file-upload';
export * from './horizontal-nav-tabs';
export * from './import-environments';
export * from './navigation';
export * from './notification-alert';
export * from './pages';
export * from './perspectives';
export * from './project-overview';
export * from './pvc';
export * from './redux';
export * from './resource-metadata';
export * from './telemetry';
export * from './yaml-templates';
export * from './notification-alert';
export * from './console-types';
export * from './storage-class-provisioner';
export * from './storage-provider';
export * from './actions';
export * from './telemetry';
export * from './topology-details';
export * from './topology';
export * from './create-resource';
export * from './user-preferences';
export * from './horizontal-nav-tabs';
export * from './import-environments';
export * from './cluster-overview';
export * from './project-overview';
export * from './dashboard-types';
export * from './breadcrumbs';
export * from './storage-class-provisioner';
export * from './yaml-templates';
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
DashboardsOverviewResourceActivity,
DashboardsOverviewPrometheusActivity,
} from '../extensions/dashboards';
import { DetailsItem } from '../extensions/details-item';
import {
FeatureFlag,
ModelFeatureFlag,
Expand Down Expand Up @@ -137,7 +138,8 @@ export type SupportedExtension =
| CustomOverviewDetailItem
| ProjectOverviewUtilizationItem
| ProjectOverviewInventoryItem
| StorageClassProvisioner;
| StorageClassProvisioner
| DetailsItem;

/**
* Schema of Console plugin's `console-extensions.json` file.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';
import * as _ from 'lodash';
import { K8sResourceCommon } from '@console/dynamic-plugin-sdk/src/extensions/console-types';
import { DetailsItem as DetailsItemExtension } from '@console/dynamic-plugin-sdk/src/extensions/details-item';
import { ResolvedExtension } from '@console/dynamic-plugin-sdk/src/types';
import { DetailsItem } from '@console/internal/components/utils/details-item';

export const ExtensionDetailsItem: ExtensionDetailsItemComponent = ({ extension, obj }) => {
const { path, title, component: Component, id } = extension.properties;
const sortWeight = extension.properties.sortWeight ?? 'none';
const value = _.get(obj, path);
if (!Component && typeof value === 'object') {
// eslint-disable-next-line no-console
console.warn(
`Invalid 'console.resource/details-item' extension: '${id}'. The value referenced at path '${path}' must be primitive if not accompanied by a custom component.`,
);
}
const content = Component ? (
<Component obj={obj} path={path} value={value} />
) : (
(value ?? '-').toString()
);

return (
<DetailsItem
obj={obj}
path={path}
label={title}
labelClassName={`details-item__sort-weight-${sortWeight}`} // for visibility
>
{content}
</DetailsItem>
);
};

type ExtensionDetailsItemProps = {
obj: K8sResourceCommon;
extension: ResolvedExtension<DetailsItemExtension>;
};
type ExtensionDetailsItemComponent = (props: ExtensionDetailsItemProps) => React.ReactElement;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from 'react';
import { useResolvedExtensions } from '@console/dynamic-plugin-sdk/src/api/useResolvedExtensions';
import { K8sResourceCommon } from '@console/dynamic-plugin-sdk/src/extensions/console-types';
import {
isDetailsItem,
DetailsItem,
DetailsItemColumn,
} from '@console/dynamic-plugin-sdk/src/extensions/details-item';
import { ResolvedExtension, ExtensionTypeGuard } from '@console/dynamic-plugin-sdk/src/types';
import { referenceFor, referenceForExtensionModel } from '@console/internal/module/k8s/k8s';

/**
* A hook to retrieve sorted details item extensions for a given resource and column.
*
* @param obj The k8s resource for which details are being rendered.
* @param column The column in which the details item will be rendered.
* @returns A sorted list of details item extensions for the given resource and column. Null safe.
*/
export const useDetailsItemExtensionsForResource: UseDetailsItemExtensionsForResource = (
obj,
column,
) => {
const typeGuard = React.useCallback<ExtensionTypeGuard<DetailsItem>>(
(e): e is DetailsItem => {
const columnMatches = e.properties.column === column;
const modelMatches = referenceFor(obj) === referenceForExtensionModel(e.properties.model);
return isDetailsItem(e) && modelMatches && columnMatches;
},
[obj, column],
);

const [extensions] = useResolvedExtensions<DetailsItem>(typeGuard);

return React.useMemo(
() =>
(extensions ?? []).sort((a, b) => {
const aWeight = Number(a.properties.sortWeight);
const bWeight = Number(b.properties.sortWeight);
if (Number.isNaN(aWeight)) {
return 1;
}
if (Number.isNaN(bWeight)) {
return -1;
}
return aWeight - bWeight;
}),
[extensions],
);
};

type UseDetailsItemExtensionsForResource = (
obj: K8sResourceCommon,
column: DetailsItemColumn,
) => ResolvedExtension<DetailsItem>[];
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { KnativeServingModel } from '../../models';
const knativeServingReference: K8sResourceKindReference = referenceForModel(KnativeServingModel);

const KnativeServingDetailsPage: React.FC<React.ComponentProps<typeof DetailsPage>> = (props) => {
const pages = [navFactory.details(DetailsForKind(props.kind)), navFactory.editYaml()];
const pages = [navFactory.details(DetailsForKind), navFactory.editYaml()];

return (
<DetailsPage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
import { serverlessTab } from '../../utils/serverless-tab-utils';

const RevisionDetailsPage: React.FC<React.ComponentProps<typeof DetailsPage>> = (props) => {
const { kindObj, match, kind } = props;
const { kindObj, match } = props;
const isAdminPerspective = useActivePerspective()[0] === 'admin';
const pages = [navFactory.details(DetailsForKind(kind)), navFactory.editYaml()];
const pages = [navFactory.details(DetailsForKind), navFactory.editYaml()];
const breadcrumbs = useTabbedTableBreadcrumbsFor(
kindObj,
match,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTriggersTechPreviewBadge } from '../../utils/hooks';
import { useTriggersBreadcrumbsFor } from './hooks';

const ClusterTriggerBindingPage: React.FC<DetailsPageProps> = (props) => {
const { kindObj, match, kind } = props;
const { kindObj, match } = props;
const breadcrumbsFor = useTriggersBreadcrumbsFor(kindObj, match);
const badge = useTriggersTechPreviewBadge(props.namespace);

Expand All @@ -16,7 +16,7 @@ const ClusterTriggerBindingPage: React.FC<DetailsPageProps> = (props) => {
badge={badge}
menuActions={Kebab.factory.common}
breadcrumbsFor={() => breadcrumbsFor}
pages={[navFactory.details(DetailsForKind(kind)), navFactory.editYaml()]}
pages={[navFactory.details(DetailsForKind), navFactory.editYaml()]}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { usePipelineTechPreviewBadge } from '../../../utils/hooks';
import { usePipelinesBreadcrumbsFor } from '../hooks';

const PipelineConditionDetailsPage: React.FC<DetailsPageProps> = (props) => {
const { kindObj, match, kind } = props;
const { kindObj, match } = props;
const breadcrumbsFor = usePipelinesBreadcrumbsFor(kindObj, match);
const badge = usePipelineTechPreviewBadge(props.namespace);

Expand All @@ -16,7 +16,7 @@ const PipelineConditionDetailsPage: React.FC<DetailsPageProps> = (props) => {
badge={badge}
menuActions={Kebab.factory.common}
breadcrumbsFor={() => breadcrumbsFor}
pages={[navFactory.details(DetailsForKind(kind)), navFactory.editYaml()]}
pages={[navFactory.details(DetailsForKind), navFactory.editYaml()]}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { usePipelineTechPreviewBadge } from '../../../utils/hooks';
import { usePipelinesBreadcrumbsFor } from '../hooks';

const PipelineResourceDetailsPage: React.FC<DetailsPageProps> = (props) => {
const { kindObj, match, kind } = props;
const { kindObj, match } = props;
const breadcrumbsFor = usePipelinesBreadcrumbsFor(kindObj, match);
const badge = usePipelineTechPreviewBadge(props.namespace);

Expand All @@ -16,7 +16,7 @@ const PipelineResourceDetailsPage: React.FC<DetailsPageProps> = (props) => {
badge={badge}
menuActions={Kebab.factory.common}
breadcrumbsFor={() => breadcrumbsFor}
pages={[navFactory.details(DetailsForKind(kind)), navFactory.editYaml()]}
pages={[navFactory.details(DetailsForKind), navFactory.editYaml()]}
/>
);
};
Expand Down

0 comments on commit 33225d2

Please sign in to comment.