Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 1877944: Show all resources for selected operator in the topology side panel #6591

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,29 @@ import { K8sResourceKind, referenceFor, modelFor } from '@console/internal/modul
type TopologyGroupResourceItemProps = {
item: K8sResourceKind;
releaseNamespace: string;
linkForResource?: (obj: K8sResourceKind) => React.ReactElement;
};

const TopologyGroupResourceItem: React.FC<TopologyGroupResourceItemProps> = ({
item,
releaseNamespace,
linkForResource,
}) => {
const {
metadata: { name, namespace },
} = item;
const kind = referenceFor(item);
const model = modelFor(kind);
const resourceNamespace = model.namespaced ? namespace || releaseNamespace : null;

const link = linkForResource ? (
linkForResource(item)
) : (
<ResourceLink kind={kind} name={name} namespace={resourceNamespace} />
);
return (
<li className="list-group-item container-fluid">
<div className="row">
<span className="col-xs-12">
<ResourceLink kind={kind} name={name} namespace={resourceNamespace} />
</span>
<span className="col-xs-12">{link}</span>
</div>
</li>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import TopologyGroupResourceItem from './TopologyGroupResourceItem';
type TopologyGroupResourceListProps = {
resources: K8sResourceKind[];
releaseNamespace: string;
linkForResource?: (obj: K8sResourceKind) => React.ReactElement;
};

const TopologyGroupResourceList: React.FC<TopologyGroupResourceListProps> = ({
resources,
releaseNamespace,
linkForResource,
}) => {
return (
<ul className="list-group">
Expand All @@ -20,6 +22,7 @@ const TopologyGroupResourceList: React.FC<TopologyGroupResourceListProps> = ({
key={resource.metadata.name}
item={resource}
releaseNamespace={releaseNamespace}
linkForResource={linkForResource}
/>
))}
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import TopologyGroupResourceList from './TopologyGroupResourceList';
type TopologyGroupResourcesPanelProps = {
manifestResources: K8sResourceKind[];
releaseNamespace: string;
linkForResource?: (obj: K8sResourceKind) => React.ReactElement;
};

const TopologyGroupResourcesPanel: React.SFC<TopologyGroupResourcesPanelProps> = ({
manifestResources,
releaseNamespace,
linkForResource,
}) => {
const kinds = manifestResources
.reduce((resourceKinds, resource) => {
Expand All @@ -29,7 +31,11 @@ const TopologyGroupResourcesPanel: React.SFC<TopologyGroupResourcesPanelProps> =
lists.push(
<div key={model.kind}>
<SidebarSectionHeading text={model.labelPlural} />
<TopologyGroupResourceList resources={resources} releaseNamespace={releaseNamespace} />
<TopologyGroupResourceList
resources={resources}
releaseNamespace={releaseNamespace}
linkForResource={linkForResource}
/>
</div>,
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,84 @@
import * as React from 'react';
import { SimpleTabNav, ResourceSummary, SectionHeading } from '@console/internal/components/utils';
import { connect } from 'react-redux';
import * as UIActions from '@console/internal/actions/ui';
import { ManagedByOperatorResourceLink } from '@console/internal/components/utils/managed-by';
import { modelFor, referenceFor, referenceForModel } from '@console/internal/module/k8s';
import {
SimpleTabNav,
ResourceSummary,
SectionHeading,
ActionsMenu,
StatusBox,
} from '@console/internal/components/utils';
import TopologyOperatorBackedResources from './TopologyOperatorBackedResources';
import { TopologyDataObject } from '../topology-types';
import { OperatorGroupData } from './operator-topology-types';
import { ManagedByOperatorResourceLink } from '@console/internal/components/utils/managed-by';
import {
ClusterServiceVersionAction,
isClusterServiceVersionAction,
useExtensions,
} from '@console/plugin-sdk/src';
import {
ClusterServiceVersionKind,
ClusterServiceVersionModel,
} from '@console/operator-lifecycle-manager/src';
import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook';
import { getOperandActions } from '@console/operator-lifecycle-manager/src/components/operand';

type PropsFromState = {
selectedDetailsTab?: any;
};

type PropsFromDispatch = {
onClickTab?: (name: string) => void;
};

const stateToProps = ({ UI }): PropsFromState => ({
selectedDetailsTab: UI.getIn(['overview', 'selectedDetailsTab']),
});

const dispatchToProps = (dispatch): PropsFromDispatch => ({
onClickTab: (name) => dispatch(UIActions.selectOverviewDetailsTab(name)),
});

export type TopologyOperatorBackedPanelProps = {
item: TopologyDataObject<OperatorGroupData>;
};

const TopologyOperatorBackedPanel: React.FC<TopologyOperatorBackedPanelProps> = ({ item }) => {
const ConnectedTopologyOperatorBackedPanel: React.FC<PropsFromState &
PropsFromDispatch &
TopologyOperatorBackedPanelProps> = ({ item, onClickTab, selectedDetailsTab }) => {
const { name, resource } = item;
const { namespace } = resource.metadata;
const csvName = resource.metadata.selfLink.split('/').pop();
const ResourcesSection = () => <TopologyOperatorBackedResources item={item} />;
const reference = referenceFor(resource);
const actionExtensions = useExtensions<ClusterServiceVersionAction>(
isClusterServiceVersionAction,
);
const menuActions = React.useMemo(() => getOperandActions(reference, actionExtensions, csvName), [
reference,
actionExtensions,
csvName,
]);
const actions = menuActions.map((a) => a(modelFor(reference), resource));
const resourcesList = React.useMemo(() => {
return {
csv: {
kind: referenceForModel(ClusterServiceVersionModel),
name: csvName,
namespace,
isList: false,
},
};
}, [csvName, namespace]);

const resources = useK8sWatchResources(resourcesList);
const ResourcesSection = () => (
<TopologyOperatorBackedResources
item={item}
csv={resources.csv.data as ClusterServiceVersionKind}
/>
);
const DetailsSection = () => (
<div className="overview__sidebar-pane-body">
<SectionHeading text="Operator Details" />
Expand All @@ -36,18 +102,39 @@ const TopologyOperatorBackedPanel: React.FC<TopologyOperatorBackedPanelProps> =
}}
/>
</div>
<div className="co-actions">
<ActionsMenu actions={actions} />
</div>
</h1>
</div>
<SimpleTabNav
tabs={[
{ name: 'Details', component: DetailsSection },
{ name: 'Resources', component: ResourcesSection },
]}
tabProps={null}
additionalClassNames="co-m-horizontal-nav__menu--within-sidebar co-m-horizontal-nav__menu--within-overview-sidebar"
/>
<StatusBox
data={resources.csv.data}
loaded={resources.csv.loaded}
loadError={resources.csv.loadError}
label="Operator Details"
>
<SimpleTabNav
selectedTab={selectedDetailsTab || 'Resources'}
onClickTab={onClickTab}
tabs={[
{ name: 'Details', component: DetailsSection },
{ name: 'Resources', component: ResourcesSection },
]}
tabProps={null}
additionalClassNames="co-m-horizontal-nav__menu--within-sidebar co-m-horizontal-nav__menu--within-overview-sidebar"
/>
</StatusBox>
</div>
);
};

const TopologyOperatorBackedPanel = connect<
PropsFromState,
PropsFromDispatch,
TopologyOperatorBackedPanelProps
>(
stateToProps,
dispatchToProps,
)(ConnectedTopologyOperatorBackedPanel);

export default TopologyOperatorBackedPanel;
Original file line number Diff line number Diff line change
@@ -1,22 +1,153 @@
import * as React from 'react';
import { Link } from 'react-router-dom';
import {
GroupVersionKind,
K8sResourceKind,
modelFor,
referenceFor,
referenceForGroupVersionKind,
referenceForModel,
} from '@console/internal/module/k8s';
import {
ClusterServiceVersionKind,
ClusterServiceVersionModel,
CRDDescription,
providedAPIsFor,
referenceForProvidedAPI,
} from '@console/operator-lifecycle-manager/src';
import {
flattenCsvResources,
linkForCsvResource,
} from '@console/operator-lifecycle-manager/src/components/k8s-resource';
import { Firehose, ResourceIcon, StatusBox } from '@console/internal/components/utils';
import { TopologyDataObject } from '../topology-types';
import TopologyGroupResourcesPanel from '../components/TopologyGroupResourcesPanel';

type OperatorResourcesProps = {
namespace: string;
resources?: {
[kind: string]: { data: K8sResourceKind[] };
};
loaded?: boolean;
loadError?: string;
flatten: (resources: { [kind: string]: { data: K8sResourceKind[] } }) => K8sResourceKind[];
linkForResource?: (obj: K8sResourceKind) => React.ReactElement;
};

const OperatorResources: React.FC<OperatorResourcesProps> = ({
namespace,
resources,
loaded,
loadError,
flatten,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just thought , why are we passing flatten as props across , can't we just use util flattenCsvResources wherever needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flatten is a function returned from a call to flattenCsvResources on line https://github.com/openshift/console/pull/6591/files#diff-fd65de433ab9e684dee076b3cc686df8R122

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah got it, my bad

linkForResource,
}) => {
const manifestResources = flatten(resources);
return (
<StatusBox data={manifestResources} loaded={loaded} loadError={loadError} label="Resources">
<TopologyGroupResourcesPanel
manifestResources={manifestResources}
releaseNamespace={namespace}
linkForResource={linkForResource}
/>
</StatusBox>
);
};

type OperatorResourcesGetterProps = {
modelReference: GroupVersionKind;
csv: ClusterServiceVersionKind;
namespace: string;
flatten: (resources: { [kind: string]: { data: K8sResourceKind[] } }) => K8sResourceKind[];
};

const OperatorResourcesGetter: React.FC<OperatorResourcesGetterProps> = ({
csv,
modelReference,
namespace,
flatten,
}) => {
const providedAPI = providedAPIsFor(csv).find(
(desc) => referenceForProvidedAPI(desc) === modelReference,
);
const linkForResource = (obj: K8sResourceKind) => {
return linkForCsvResource(obj, providedAPI, csv.metadata.name);
};
const defaultResources = ['Deployment', 'Service', 'ReplicaSet', 'Pod', 'Secret', 'ConfigMap'];
const resourcesToGet =
providedAPI?.resources ??
(defaultResources.map((kind) => ({
kind,
})) as CRDDescription['resources']);

const firehoseResources = resourcesToGet.reduce((acc, descriptor) => {
const { name, kind, version } = descriptor;
const group = name ? name.substring(name.indexOf('.') + 1) : '';
const reference = group ? referenceForGroupVersionKind(group)(version)(kind) : kind;
const model = modelFor(reference);
acc.push({
prop: kind,
kind: model && !model.crd ? kind : reference,
namespaced: model ? model.namespaced : true,
namespace,
isList: true,
optional: true,
});
return acc;
}, []);

return (
<Firehose resources={firehoseResources}>
<OperatorResources
namespace={namespace}
flatten={flatten}
linkForResource={linkForResource}
/>
</Firehose>
);
};

type TopologyOperatorBackedResourcesProps = {
item: TopologyDataObject;
csv: ClusterServiceVersionKind;
};

const TopologyOperatorBackedResources: React.FC<TopologyOperatorBackedResourcesProps> = ({
item,
csv,
}) => {
const { groupResources = [] } = item;
const finalRes = groupResources.map((val) => val.resource).filter((r) => r !== undefined);
const { resource } = item;
const { namespace } = resource.metadata;
const reference = referenceFor(resource);
const flatten = flattenCsvResources(resource);
const getManagedByCSVResourceLink = () => {
const model = modelFor(referenceFor(csv));
const { name } = csv.metadata;
const { kind } = model;

const link = `/k8s/ns/${namespace}/${referenceForModel(ClusterServiceVersionModel)}/${name}`;

return (
<div className="co-m-pane__heading-owner">
Managed by{' '}
<span className="co-resource-item">
<ResourceIcon kind={kind} />
<Link to={link} className="co-resource-item__resource-name" data-test-operand-link={name}>
{name}
</Link>
</span>
</div>
);
};

return (
<div className="overview__sidebar-pane-body">
<TopologyGroupResourcesPanel
manifestResources={finalRes}
releaseNamespace={item.resources.obj.metadata.namespace}
{getManagedByCSVResourceLink()}
<OperatorResourcesGetter
csv={csv}
flatten={flatten}
namespace={namespace}
modelReference={reference}
/>
</div>
);
Expand Down