diff --git a/.changeset/long-sheep-exercise.md b/.changeset/long-sheep-exercise.md new file mode 100644 index 0000000000..f5e692f55e --- /dev/null +++ b/.changeset/long-sheep-exercise.md @@ -0,0 +1,16 @@ +--- +'@backstage/plugin-catalog-graph': minor +--- + +Add the entire `Entity` to `EntityNodeData` and deprecate `name`, `kind`, `title`, `namespace` and `spec`. + +To get the deprecated properties in your custom component you can use: + +```typescript +import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; + +const { + kind, + metadata: { name, namespace = DEFAULT_NAMESPACE, title }, +} = entity; +``` diff --git a/plugins/catalog-graph/README.md b/plugins/catalog-graph/README.md index f723523716..010a129b0f 100644 --- a/plugins/catalog-graph/README.md +++ b/plugins/catalog-graph/README.md @@ -85,6 +85,55 @@ To use the catalog graph plugin, you have to add some things to your Backstage a ``` +### Customization + +Copy the default implementation `DefaultRenderNode.tsx` and add more classes to the styles: + +```typescript +const useStyles = makeStyles( + theme => ({ + node: { + … + '&.system': { + fill: '#F5DC70', + stroke: '#F2CE34', + }, + '&.domain': { + fill: '#F5DC70', + stroke: '#F2CE34', + }, + … +); +``` + +Now you can use the new classes in your component with `className={classNames(classes.node, kind?.toLowerCase(), type?.toLowerCase())}` + +```tsx +return ( + + + + {displayTitle} + + +); +``` + ## Development Run `yarn` in the root of this plugin to install all dependencies and then `yarn start` to run a [development version](./dev/index.tsx) of this plugin. diff --git a/plugins/catalog-graph/api-report.md b/plugins/catalog-graph/api-report.md index 98a68d8aa2..ba75943843 100644 --- a/plugins/catalog-graph/api-report.md +++ b/plugins/catalog-graph/api-report.md @@ -8,6 +8,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { DependencyGraphTypes } from '@backstage/core-components'; +import { Entity } from '@backstage/catalog-model'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; import { JsonObject } from '@backstage/types'; @@ -90,14 +91,15 @@ export type EntityNode = DependencyGraphTypes.DependencyNode; // @public export type EntityNodeData = { + entity: Entity; + focused?: boolean; + color?: 'primary' | 'secondary' | 'default'; + onClick?: MouseEventHandler; name: string; kind?: string; title?: string; namespace: string; spec?: JsonObject; - focused?: boolean; - color?: 'primary' | 'secondary' | 'default'; - onClick?: MouseEventHandler; }; // @public diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index 7976fa66bb..99942b9a5c 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -37,7 +37,7 @@ import { EntityNode, EntityRelationsGraph, } from '../EntityRelationsGraph'; -import { EntityRelationsGraphProps } from '../EntityRelationsGraph/EntityRelationsGraph'; +import { EntityRelationsGraphProps } from '../EntityRelationsGraph'; const useStyles = makeStyles( { @@ -97,7 +97,7 @@ export const CatalogGraphCard = ( }); analytics.captureEvent( 'click', - node.title ?? humanizeEntityRef(nodeEntityName), + node.entity.metadata.title ?? humanizeEntityRef(nodeEntityName), { attributes: { to: path } }, ); navigate(path); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 92da650359..73d79978e9 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -157,14 +157,14 @@ export const CatalogGraphPage = ( analytics.captureEvent( 'click', - node.title ?? humanizeEntityRef(nodeEntityName), + node.entity.metadata.title ?? humanizeEntityRef(nodeEntityName), { attributes: { to: path } }, ); navigate(path); } else { analytics.captureEvent( 'click', - node.title ?? humanizeEntityRef(nodeEntityName), + node.entity.metadata.title ?? humanizeEntityRef(nodeEntityName), ); setRootEntityNames([nodeEntityName]); } diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/CustomLabel.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderLabel.test.tsx similarity index 93% rename from plugins/catalog-graph/src/components/EntityRelationsGraph/CustomLabel.test.tsx rename to plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderLabel.test.tsx index ab280aea81..14692f1a7b 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/CustomLabel.test.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderLabel.test.tsx @@ -20,13 +20,13 @@ import { } from '@backstage/catalog-model'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import { CustomLabel } from './CustomLabel'; +import { DefaultRenderLabel } from './DefaultRenderLabel'; describe('', () => { test('renders label', () => { render( - ', () => { test('renders label with multiple relations', () => { render( - ) { const classes = useStyles(); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/CustomNode.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.test.tsx similarity index 70% rename from plugins/catalog-graph/src/components/EntityRelationsGraph/CustomNode.test.tsx rename to plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.test.tsx index 7a34bbf688..1f3d77e1ae 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/CustomNode.test.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.test.tsx @@ -17,21 +17,30 @@ import { renderInTestApp } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; import React from 'react'; -import { CustomNode } from './CustomNode'; +import { DefaultRenderNode } from './DefaultRenderNode'; import userEvent from '@testing-library/user-event'; describe('', () => { test('renders node', async () => { await renderInTestApp( - , @@ -43,13 +52,22 @@ describe('', () => { test('renders node, skips default namespace', async () => { await renderInTestApp( - , @@ -62,14 +80,23 @@ describe('', () => { const onClick = jest.fn(); await renderInTestApp( - , @@ -83,14 +110,24 @@ describe('', () => { test('renders title if entity has one', async () => { await renderInTestApp( - , diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/CustomNode.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx similarity index 94% rename from plugins/catalog-graph/src/components/EntityRelationsGraph/CustomNode.tsx rename to plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx index 1229ad28f9..88de8df686 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/CustomNode.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx @@ -20,6 +20,7 @@ import classNames from 'classnames'; import React, { useLayoutEffect, useRef, useState } from 'react'; import { EntityKindIcon } from './EntityKindIcon'; import { EntityNodeData } from './types'; +import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; const useStyles = makeStyles( theme => ({ @@ -56,17 +57,8 @@ const useStyles = makeStyles( { name: 'PluginCatalogGraphCustomNode' }, ); -export function CustomNode({ - node: { - id, - kind, - namespace, - name, - color = 'default', - focused, - title, - onClick, - }, +export function DefaultRenderNode({ + node: { id, entity, color = 'default', focused, onClick }, }: DependencyGraphTypes.RenderNodeProps) { const classes = useStyles(); const [width, setWidth] = useState(0); @@ -88,6 +80,11 @@ export function CustomNode({ } }, [width, height]); + const { + kind, + metadata: { name, namespace = DEFAULT_NAMESPACE, title }, + } = entity; + const padding = 10; const iconSize = height; const paddedIconWidth = kind ? iconSize + padding : 0; diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx index 2451f4913a..654aaff969 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx @@ -26,8 +26,8 @@ import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { CircularProgress, makeStyles, useTheme } from '@material-ui/core'; import classNames from 'classnames'; import React, { MouseEvent, useEffect, useMemo } from 'react'; -import { CustomLabel } from './CustomLabel'; -import { CustomNode } from './CustomNode'; +import { DefaultRenderLabel } from './DefaultRenderLabel'; +import { DefaultRenderNode } from './DefaultRenderNode'; import { ALL_RELATION_PAIRS, RelationPairs } from './relations'; import { Direction, EntityEdge, EntityNode } from './types'; import { useEntityRelationNodesAndEdges } from './useEntityRelationNodesAndEdges'; @@ -51,7 +51,7 @@ const useStyles = makeStyles( width: '100%', flex: 1, // Right now there is no good way to style edges between nodes, we have to - // fallback to these hacks: + // fall back to these hacks: '& path[marker-end]': { transition: 'filter 0.1s ease-in-out', }, @@ -144,8 +144,8 @@ export const EntityRelationsGraph = (props: EntityRelationsGraphProps) => { ; */ export type EntityNodeData = { /** - * Name of the entity. + * The Entity */ - name: string; - /** - * Optional kind of the entity. - */ - kind?: string; - /** - * Optional title of the entity. - */ - title?: string; - /** - * Namespace of the entity. - */ - namespace: string; - /** - * Optional spec of the entity. - */ - spec?: JsonObject; + entity: Entity; /** * Whether the entity is focused, optional, defaults to false. Focused * entities are highlighted in the graph. @@ -81,6 +66,33 @@ export type EntityNodeData = { * Optional click handler. */ onClick?: MouseEventHandler; + + /** + * Name of the entity. + * @deprecated use {@link EntityNodeData#entity} instead + */ + name: string; + /** + * Optional kind of the entity. + * @deprecated use {@link EntityNodeData#entity} instead + */ + kind?: string; + /** + * Optional title of the entity. + * @deprecated use {@link EntityNodeData#entity} instead + */ + title?: string; + /** + * Namespace of the entity. + * @deprecated use {@link EntityNodeData#entity} instead + * The Entity + */ + namespace: string; + /** + * Optional spec of the entity. + * @deprecated use {@link EntityNodeData#entity} instead + */ + spec?: JsonObject; }; /** diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts index d17e8b1c9d..8c7b59b9c4 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { + DEFAULT_NAMESPACE, Entity, RELATION_HAS_PART, RELATION_OWNED_BY, @@ -25,6 +26,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import { filter, keyBy } from 'lodash'; import { useEntityRelationGraph as useEntityRelationGraphMocked } from './useEntityRelationGraph'; import { useEntityRelationNodesAndEdges } from './useEntityRelationNodesAndEdges'; +import { EntityNode } from './types'; jest.mock('./useEntityRelationGraph'); @@ -32,83 +34,92 @@ const useEntityRelationGraph = useEntityRelationGraphMocked as jest.Mock< ReturnType >; +const entities: { [ref: string]: Entity } = { + 'b:d/c': { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'c', + namespace: 'd', + }, + relations: [ + { + targetRef: 'k:d/a1', + type: RELATION_OWNER_OF, + }, + { + targetRef: 'b:d/c1', + type: RELATION_HAS_PART, + }, + ], + }, + 'k:d/a1': { + apiVersion: 'a', + kind: 'k', + metadata: { + name: 'a1', + namespace: 'd', + }, + relations: [ + { + targetRef: 'b:d/c', + type: RELATION_OWNED_BY, + }, + { + targetRef: 'b:d/c1', + type: RELATION_OWNED_BY, + }, + ], + }, + 'b:d/c1': { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'c1', + namespace: 'd', + }, + relations: [ + { + targetRef: 'b:d/c', + type: RELATION_PART_OF, + }, + { + targetRef: 'k:d/a1', + type: RELATION_OWNER_OF, + }, + { + targetRef: 'b:d/c2', + type: RELATION_HAS_PART, + }, + ], + }, + 'b:d/c2': { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'c2', + namespace: 'd', + }, + relations: [ + { + targetRef: 'b:d/c1', + type: RELATION_PART_OF, + }, + ], + }, +}; + +function deprecatedProperties(entity: Entity): Partial { + return { + kind: entity.kind, + name: entity.metadata.name, + namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, + title: entity.metadata.title, + }; +} + describe('useEntityRelationNodesAndEdges', () => { beforeEach(() => { - const entities: { [ref: string]: Entity } = { - 'b:d/c': { - apiVersion: 'a', - kind: 'b', - metadata: { - name: 'c', - namespace: 'd', - }, - relations: [ - { - targetRef: 'k:d/a1', - type: RELATION_OWNER_OF, - }, - { - targetRef: 'b:d/c1', - type: RELATION_HAS_PART, - }, - ], - }, - 'k:d/a1': { - apiVersion: 'a', - kind: 'k', - metadata: { - name: 'a1', - namespace: 'd', - }, - relations: [ - { - targetRef: 'b:d/c', - type: RELATION_OWNED_BY, - }, - { - targetRef: 'b:d/c1', - type: RELATION_OWNED_BY, - }, - ], - }, - 'b:d/c1': { - apiVersion: 'a', - kind: 'b', - metadata: { - name: 'c1', - namespace: 'd', - }, - relations: [ - { - targetRef: 'b:d/c', - type: RELATION_PART_OF, - }, - { - targetRef: 'k:d/a1', - type: RELATION_OWNER_OF, - }, - { - targetRef: 'b:d/c2', - type: RELATION_HAS_PART, - }, - ], - }, - 'b:d/c2': { - apiVersion: 'a', - kind: 'b', - metadata: { - name: 'c2', - namespace: 'd', - }, - relations: [ - { - targetRef: 'b:d/c1', - type: RELATION_PART_OF, - }, - ], - }, - }; - useEntityRelationGraph.mockImplementation(({ filter: { kinds } }) => ({ loading: false, entities: keyBy( @@ -184,33 +195,29 @@ describe('useEntityRelationNodesAndEdges', () => { color: 'secondary', focused: true, id: 'b:d/c', - kind: 'b', - name: 'c', - namespace: 'd', + entity: entities['b:d/c'], + ...deprecatedProperties(entities['b:d/c']), }, { color: 'primary', focused: false, id: 'k:d/a1', - kind: 'k', - name: 'a1', - namespace: 'd', + entity: entities['k:d/a1'], + ...deprecatedProperties(entities['k:d/a1']), }, { color: 'primary', focused: false, id: 'b:d/c1', - kind: 'b', - name: 'c1', - namespace: 'd', + entity: entities['b:d/c1'], + ...deprecatedProperties(entities['b:d/c1']), }, { color: 'primary', focused: false, id: 'b:d/c2', - kind: 'b', - name: 'c2', - namespace: 'd', + entity: entities['b:d/c2'], + ...deprecatedProperties(entities['b:d/c2']), }, ]); expect(edges).toEqual([ @@ -257,33 +264,29 @@ describe('useEntityRelationNodesAndEdges', () => { color: 'secondary', focused: true, id: 'b:d/c', - kind: 'b', - name: 'c', - namespace: 'd', + entity: entities['b:d/c'], + ...deprecatedProperties(entities['b:d/c']), }, { color: 'primary', focused: false, id: 'k:d/a1', - kind: 'k', - name: 'a1', - namespace: 'd', + entity: entities['k:d/a1'], + ...deprecatedProperties(entities['k:d/a1']), }, { color: 'primary', focused: false, id: 'b:d/c1', - kind: 'b', - name: 'c1', - namespace: 'd', + entity: entities['b:d/c1'], + ...deprecatedProperties(entities['b:d/c1']), }, { color: 'primary', focused: false, id: 'b:d/c2', - kind: 'b', - name: 'c2', - namespace: 'd', + entity: entities['b:d/c2'], + ...deprecatedProperties(entities['b:d/c2']), }, ]); expect(edges).toEqual([ @@ -330,33 +333,29 @@ describe('useEntityRelationNodesAndEdges', () => { color: 'secondary', focused: true, id: 'b:d/c', - kind: 'b', - name: 'c', - namespace: 'd', + entity: entities['b:d/c'], + ...deprecatedProperties(entities['b:d/c']), }, { color: 'primary', focused: false, id: 'k:d/a1', - kind: 'k', - name: 'a1', - namespace: 'd', + entity: entities['k:d/a1'], + ...deprecatedProperties(entities['k:d/a1']), }, { color: 'primary', focused: false, id: 'b:d/c1', - kind: 'b', - name: 'c1', - namespace: 'd', + entity: entities['b:d/c1'], + ...deprecatedProperties(entities['b:d/c1']), }, { color: 'primary', focused: false, id: 'b:d/c2', - kind: 'b', - name: 'c2', - namespace: 'd', + entity: entities['b:d/c2'], + ...deprecatedProperties(entities['b:d/c2']), }, ]); expect(edges).toEqual([ @@ -433,33 +432,29 @@ describe('useEntityRelationNodesAndEdges', () => { color: 'secondary', focused: true, id: 'b:d/c', - kind: 'b', - name: 'c', - namespace: 'd', + entity: entities['b:d/c'], + ...deprecatedProperties(entities['b:d/c']), }, { color: 'primary', focused: false, id: 'k:d/a1', - kind: 'k', - name: 'a1', - namespace: 'd', + entity: entities['k:d/a1'], + ...deprecatedProperties(entities['k:d/a1']), }, { color: 'primary', focused: false, id: 'b:d/c1', - kind: 'b', - name: 'c1', - namespace: 'd', + entity: entities['b:d/c1'], + ...deprecatedProperties(entities['b:d/c1']), }, { color: 'primary', focused: false, id: 'b:d/c2', - kind: 'b', - name: 'c2', - namespace: 'd', + entity: entities['b:d/c2'], + ...deprecatedProperties(entities['b:d/c2']), }, ]); expect(edges).toEqual([ @@ -534,33 +529,29 @@ describe('useEntityRelationNodesAndEdges', () => { color: 'secondary', focused: true, id: 'b:d/c', - kind: 'b', - name: 'c', - namespace: 'd', + entity: entities['b:d/c'], + ...deprecatedProperties(entities['b:d/c']), }, { color: 'primary', focused: false, id: 'k:d/a1', - kind: 'k', - name: 'a1', - namespace: 'd', + entity: entities['k:d/a1'], + ...deprecatedProperties(entities['k:d/a1']), }, { color: 'primary', focused: false, id: 'b:d/c1', - kind: 'b', - name: 'c1', - namespace: 'd', + entity: entities['b:d/c1'], + ...deprecatedProperties(entities['b:d/c1']), }, { color: 'secondary', focused: true, id: 'b:d/c2', - kind: 'b', - name: 'c2', - namespace: 'd', + entity: entities['b:d/c2'], + ...deprecatedProperties(entities['b:d/c2']), }, ]); expect(edges).toEqual([ @@ -606,33 +597,29 @@ describe('useEntityRelationNodesAndEdges', () => { color: 'secondary', focused: true, id: 'b:d/c', - kind: 'b', - name: 'c', - namespace: 'd', + entity: entities['b:d/c'], + ...deprecatedProperties(entities['b:d/c']), }, { color: 'primary', focused: false, id: 'k:d/a1', - kind: 'k', - name: 'a1', - namespace: 'd', + entity: entities['k:d/a1'], + ...deprecatedProperties(entities['k:d/a1']), }, { color: 'primary', focused: false, id: 'b:d/c1', - kind: 'b', - name: 'c1', - namespace: 'd', + entity: entities['b:d/c1'], + ...deprecatedProperties(entities['b:d/c1']), }, { color: 'primary', focused: false, id: 'b:d/c2', - kind: 'b', - name: 'c2', - namespace: 'd', + entity: entities['b:d/c2'], + ...deprecatedProperties(entities['b:d/c2']), }, ]); expect(edges).toEqual([ @@ -658,6 +645,7 @@ describe('useEntityRelationNodesAndEdges', () => { }); const { nodes, edges, loading, error } = result.current; + // nodes?.sort((a, b) => a.id.localeCompare(b.id)); expect(loading).toBe(false); expect(error).toBeUndefined(); @@ -666,25 +654,22 @@ describe('useEntityRelationNodesAndEdges', () => { color: 'secondary', focused: true, id: 'b:d/c', - kind: 'b', - name: 'c', - namespace: 'd', + entity: entities['b:d/c'], + ...deprecatedProperties(entities['b:d/c']), }, { color: 'primary', focused: false, id: 'b:d/c1', - kind: 'b', - name: 'c1', - namespace: 'd', + entity: entities['b:d/c1'], + ...deprecatedProperties(entities['b:d/c1']), }, { color: 'primary', focused: false, id: 'b:d/c2', - kind: 'b', - name: 'c2', - namespace: 'd', + entity: entities['b:d/c2'], + ...deprecatedProperties(entities['b:d/c2']), }, ]); expect(edges).toEqual([ diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts index 2e177e2b75..4302957d5b 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { MouseEvent, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import { RelationPairs, ALL_RELATION_PAIRS } from './relations'; import { EntityEdge, EntityNode } from './types'; import { useEntityRelationGraph } from './useEntityRelationGraph'; +import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; /** * Generate nodes and edges to render the entity graph. @@ -71,13 +71,15 @@ export function useEntityRelationNodesAndEdges({ const focused = rootEntityRefs.includes(entityRef); const node: EntityNode = { id: entityRef, - title: entity.metadata?.title ?? undefined, - kind: entity.kind, - name: entity.metadata.name, - namespace: entity.metadata.namespace ?? DEFAULT_NAMESPACE, - spec: entity.spec ?? undefined, + entity, focused, color: focused ? 'secondary' : 'primary', + // @deprecated + kind: entity.kind, + name: entity.metadata.name, + namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, + title: entity.metadata.title, + spec: entity.spec, }; if (onNodeClick) {