diff --git a/base/202-extension-crd.yaml b/base/202-extension-crd.yaml
index 5f599c3f2..0dba28a06 100644
--- a/base/202-extension-crd.yaml
+++ b/base/202-extension-crd.yaml
@@ -47,7 +47,7 @@ spec:
jsonPath: .spec.name
- name: Display name
type: string
- jsonPath: .spec.displayname
+ jsonPath: .spec.displayName
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
diff --git a/docs/extensions.md b/docs/extensions.md
index 916869652..923247d1b 100644
--- a/docs/extensions.md
+++ b/docs/extensions.md
@@ -41,12 +41,13 @@ See the [Example: Register a CronJob extension](#example-register-a-cronjob-exte
#### ExtensionSpec
-| Variable Name | Type | Required | Default | Description |
-|---------------|-------------------|----------|---------|------------------------------------------------------------------|
-| apiVersion | string | Yes | - | Extension resource group |
-| name | string | Yes | - | Extension resource name |
-| displayname | string | Yes | - | Display name in the Dashboard UI |
-| namespaced | boolean | No | true | Specifies whether the Extension represents a namespaced resource |
+| Variable Name | Type | Required | Default | Description |
+|-----------------------------|-------------------|----------|---------|------------------------------------------------------------------|
+| apiVersion | string | Yes | - | Extension resource group |
+| name | string | Yes | - | Extension resource name |
+| disableResourceDetailsLinks | boolean | No | false | Disable display of links to resource details pages |
+| displayName | string | Yes | - | Display name in the Dashboard UI |
+| namespaced | boolean | No | true | Specifies whether the Extension represents a namespaced resource |
### Example: Register a CronJob extension
@@ -61,7 +62,7 @@ metadata:
spec:
apiVersion: batch/v1
name: cronjobs
- displayname: k8s cronjobs
+ displayName: k8s cronjobs
EOF
```
diff --git a/packages/e2e/cypress/e2e/common/extensions.cy.js b/packages/e2e/cypress/e2e/common/extensions.cy.js
index 1058ab115..5bf0d22e3 100644
--- a/packages/e2e/cypress/e2e/common/extensions.cy.js
+++ b/packages/e2e/cypress/e2e/common/extensions.cy.js
@@ -37,7 +37,7 @@ metadata:
spec:
apiVersion: core/v1
name: namespaces
- displayname: Namespaces
+ displayName: Namespaces
`);
cy.contains(`.${carbonPrefix}--side-nav a`, 'Namespaces').click();
diff --git a/src/api/extensions.js b/src/api/extensions.js
index 6004553f5..44b7ef00d 100644
--- a/src/api/extensions.js
+++ b/src/api/extensions.js
@@ -25,12 +25,19 @@ export function useExtensions(params, queryConfig) {
return {
...query,
data: (data || []).map(({ spec }) => {
- const { displayname: displayName, name, namespaced } = spec;
+ const {
+ disableResourceDetailsLinks,
+ displayname, // keep for backwards compatibility for a few releases
+ displayName,
+ name,
+ namespaced
+ } = spec;
const [apiGroup, apiVersion] = spec.apiVersion.split('/');
return {
apiGroup,
apiVersion,
- displayName,
+ disableResourceDetailsLinks,
+ displayName: displayName || displayname,
name,
namespaced
};
diff --git a/src/api/extensions.test.js b/src/api/extensions.test.js
index 0e5a859a6..99f5f2960 100644
--- a/src/api/extensions.test.js
+++ b/src/api/extensions.test.js
@@ -15,6 +15,40 @@ import * as API from './extensions';
import * as utils from './utils';
it('useExtensions', () => {
+ const name = 'fake_name';
+ const group = 'fake_group';
+ const version = 'fake_version';
+ const apiVersion = `${group}/${version}`;
+ const displayName = 'fake_displayName';
+ const namespaced = true;
+ const query = {
+ data: [{ spec: { apiVersion, displayName, name, namespaced } }]
+ };
+ const params = { fake: 'params' };
+ vi.spyOn(utils, 'useCollection').mockImplementation(() => query);
+ const extensions = API.useExtensions(params);
+ expect(utils.useCollection).toHaveBeenCalledWith(
+ expect.objectContaining({
+ group: utils.dashboardAPIGroup,
+ kind: 'extensions',
+ params,
+ version: 'v1alpha1'
+ })
+ );
+ expect(extensions).toEqual({
+ data: [
+ {
+ apiGroup: group,
+ apiVersion: version,
+ displayName,
+ name,
+ namespaced
+ }
+ ]
+ });
+});
+
+it('useExtensions displayname backwards compatibility', () => {
const name = 'fake_name';
const group = 'fake_group';
const version = 'fake_version';
diff --git a/src/containers/ResourceList/ResourceList.jsx b/src/containers/ResourceList/ResourceList.jsx
index 9e7c5551f..4b4a09226 100644
--- a/src/containers/ResourceList/ResourceList.jsx
+++ b/src/containers/ResourceList/ResourceList.jsx
@@ -25,7 +25,9 @@ import ListPageLayout from '../ListPageLayout';
import {
useAPIResource,
useCustomResources,
- useSelectedNamespace
+ useExtensions,
+ useSelectedNamespace,
+ useTenantNamespaces
} from '../../api';
export function ResourceListContainer() {
@@ -35,18 +37,29 @@ export function ResourceListContainer() {
const matches = useMatches();
const params = useParams();
+ const tenantNamespaces = useTenantNamespaces();
+ const { data: extensions = [] } = useExtensions(
+ {
+ namespace: tenantNamespaces[0] || ALL_NAMESPACES
+ },
+ { disableWebSocket: true, retryOnMount: false }
+ );
+
const { namespace: namespaceParam } = params;
const match = matches.at(-1);
const handle = match.handle || {};
let { group, kind, version } = handle;
const { resourceURL, title } = handle;
- let isExtension = false;
+ let extension;
if (!(group && kind && version)) {
// we're on a kubernetes resource extension page
// grab values directly from the URL
({ group, kind, version } = params);
- isExtension = true;
+ extension = extensions?.find(
+ ({ apiGroup, apiVersion, name }) =>
+ apiGroup === group && apiVersion === version && kind === name
+ );
}
const { selectedNamespace } = useSelectedNamespace();
@@ -59,12 +72,12 @@ export function ResourceListContainer() {
data: apiResource,
error: apiResourceError,
isLoading: isLoadingAPIResource
- } = useAPIResource({ group, kind, version }, { enabled: isExtension });
- const isNamespaced = isExtension
+ } = useAPIResource({ group, kind, version }, { enabled: !!extension });
+ const isNamespaced = extension
? !isLoadingAPIResource && apiResource?.namespaced
: handle?.isNamespaced;
- if (isExtension && typeof apiResource?.namespaced !== 'undefined') {
+ if (extension && typeof apiResource?.namespaced !== 'undefined') {
// dynamically toggle the namespace dropdown behaviour depending on
// whether the kind is namespaced or cluster-scoped
match.handle.isNamespaced = isNamespaced;
@@ -83,7 +96,7 @@ export function ResourceListContainer() {
version
},
{
- enabled: !isExtension || (!isLoadingAPIResource && !apiResourceError)
+ enabled: !extension || (!isLoadingAPIResource && !apiResourceError)
}
);
@@ -176,7 +189,7 @@ export function ResourceListContainer() {
})
}
].filter(Boolean)}
- loading={(isExtension && isLoadingAPIResource) || isLoadingResources}
+ loading={(extension && isLoadingAPIResource) || isLoadingResources}
rows={paginatedResources.map(resource => {
const {
creationTimestamp,
@@ -185,15 +198,19 @@ export function ResourceListContainer() {
uid
} = resource.metadata;
+ let resourceLink;
+ if (!extension?.disableResourceDetailsLinks) {
+ resourceLink = getResourceURL({ name, resourceNamespace });
+ }
+
return {
id: uid,
- name: (
-
+ name: resourceLink ? (
+
{name}
+ ) : (
+ name
),
namespace: resourceNamespace,
createdTime: