mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
plugin details right panel tab (#97354)
* plugin details right panel tab * fix betterer * useMedia hook, use function for currentIdPage instead of state * Rename PluginDetailsRightPanel to PluginDetailsPanel * nit changes * remove maxWidth for pluginDetailsPanel if screen is narrow * fix width prop * Add tests * Rename PluginDetailsRight Panel file, rename info prop, fix the latestVersion * delete console log * move latestVersion from info arrya * fix latestVersion test --------- Co-authored-by: Esteban Beltran <esteban@academo.me>
This commit is contained in:
parent
b8a4784a50
commit
2f40a93bf8
@ -3,9 +3,11 @@ import { useMemo } from 'react';
|
|||||||
|
|
||||||
import { AppPlugin, GrafanaTheme2, PluginContextProvider, UrlQueryMap } from '@grafana/data';
|
import { AppPlugin, GrafanaTheme2, PluginContextProvider, UrlQueryMap } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
|
import { PageInfoItem } from '@grafana/runtime/src/components/PluginPage';
|
||||||
import { CellProps, Column, InteractiveTable, Stack, useStyles2 } from '@grafana/ui';
|
import { CellProps, Column, InteractiveTable, Stack, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { Changelog } from '../components/Changelog';
|
import { Changelog } from '../components/Changelog';
|
||||||
|
import { PluginDetailsPanel } from '../components/PluginDetailsPanel';
|
||||||
import { VersionList } from '../components/VersionList';
|
import { VersionList } from '../components/VersionList';
|
||||||
import { usePluginConfig } from '../hooks/usePluginConfig';
|
import { usePluginConfig } from '../hooks/usePluginConfig';
|
||||||
import { CatalogPlugin, Permission, PluginTabIds } from '../types';
|
import { CatalogPlugin, Permission, PluginTabIds } from '../types';
|
||||||
@ -16,13 +18,15 @@ import { PluginUsage } from './PluginUsage';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
plugin: CatalogPlugin;
|
plugin: CatalogPlugin;
|
||||||
|
info: PageInfoItem[];
|
||||||
queryParams: UrlQueryMap;
|
queryParams: UrlQueryMap;
|
||||||
pageId: string;
|
pageId: string;
|
||||||
|
showDetails: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Cell<T extends keyof Permission = keyof Permission> = CellProps<Permission, Permission[T]>;
|
type Cell<T extends keyof Permission = keyof Permission> = CellProps<Permission, Permission[T]>;
|
||||||
|
|
||||||
export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.Element {
|
export function PluginDetailsBody({ plugin, queryParams, pageId, info, showDetails }: Props): JSX.Element {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const { value: pluginConfig } = usePluginConfig(plugin);
|
const { value: pluginConfig } = usePluginConfig(plugin);
|
||||||
|
|
||||||
@ -77,6 +81,14 @@ export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.E
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pageId === PluginTabIds.PLUGINDETAILS && config.featureToggles.pluginsDetailsRightPanel && showDetails) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PluginDetailsPanel pluginExtentionsInfo={info} plugin={plugin} width={'auto'} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Permissions will be returned in the iam field for installed plugins and in the details.iam field when fetching details from gcom
|
// Permissions will be returned in the iam field for installed plugins and in the details.iam field when fetching details from gcom
|
||||||
const permissions = plugin.iam?.permissions || plugin.details?.iam?.permissions;
|
const permissions = plugin.iam?.permissions || plugin.details?.iam?.permissions;
|
||||||
|
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
import { render, screen } from 'test/test-utils';
|
||||||
|
|
||||||
|
import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
|
import { CatalogPlugin } from '../types';
|
||||||
|
|
||||||
|
import { PluginDetailsPage } from './PluginDetailsPage';
|
||||||
|
|
||||||
|
const plugin: CatalogPlugin = {
|
||||||
|
description: 'Test plugin description',
|
||||||
|
downloads: 1000,
|
||||||
|
hasUpdate: false,
|
||||||
|
id: 'test-plugin',
|
||||||
|
info: {
|
||||||
|
logos: {
|
||||||
|
small: 'small-logo-url',
|
||||||
|
large: 'large-logo-url',
|
||||||
|
},
|
||||||
|
keywords: ['test', 'plugin'],
|
||||||
|
},
|
||||||
|
isDev: false,
|
||||||
|
isCore: false,
|
||||||
|
isEnterprise: false,
|
||||||
|
isInstalled: true,
|
||||||
|
isDisabled: false,
|
||||||
|
isDeprecated: false,
|
||||||
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
|
isPublished: true,
|
||||||
|
name: 'Test Plugin',
|
||||||
|
orgName: 'Test Org',
|
||||||
|
signature: PluginSignatureStatus.valid,
|
||||||
|
signatureType: PluginSignatureType.grafana,
|
||||||
|
signatureOrg: 'Test Signature Org',
|
||||||
|
popularity: 4,
|
||||||
|
publishedAt: '2023-01-01',
|
||||||
|
type: PluginType.app,
|
||||||
|
updatedAt: '2023-12-01',
|
||||||
|
installedVersion: '1.0.0',
|
||||||
|
details: {
|
||||||
|
readme: 'Test readme',
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
version: '1.0.0',
|
||||||
|
createdAt: '2023-01-01',
|
||||||
|
isCompatible: true,
|
||||||
|
grafanaDependency: '>=9.0.0',
|
||||||
|
angularDetected: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: 'Website',
|
||||||
|
url: 'https://test-plugin.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
grafanaDependency: '>=9.0.0',
|
||||||
|
statusContext: 'stable',
|
||||||
|
},
|
||||||
|
angularDetected: false,
|
||||||
|
isFullyInstalled: true,
|
||||||
|
accessControl: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('../state/hooks', () => ({
|
||||||
|
useGetSingle: jest.fn(),
|
||||||
|
useFetchStatus: jest.fn().mockReturnValue({ isLoading: false }),
|
||||||
|
useFetchDetailsStatus: () => ({ isLoading: false }),
|
||||||
|
useIsRemotePluginsAvailable: () => false,
|
||||||
|
useInstallStatus: () => ({ error: null, isInstalling: false }),
|
||||||
|
useUninstallStatus: () => ({ error: null, isUninstalling: false }),
|
||||||
|
useInstall: () => jest.fn(),
|
||||||
|
useUninstall: () => jest.fn(),
|
||||||
|
useUnsetInstall: () => jest.fn(),
|
||||||
|
useFetchDetailsLazy: () => jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockUseGetSingle = jest.requireMock('../state/hooks').useGetSingle;
|
||||||
|
|
||||||
|
describe('PluginDetailsPage', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(console, 'error').mockImplementation();
|
||||||
|
mockUseGetSingle.mockReturnValue(plugin);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show loader when fetching plugin details', () => {
|
||||||
|
jest.requireMock('../state/hooks').useFetchStatus.mockReturnValueOnce({ isLoading: true });
|
||||||
|
render(<PluginDetailsPage pluginId="test-plugin" />);
|
||||||
|
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show not found component when plugin doesnt exist', () => {
|
||||||
|
mockUseGetSingle.mockReturnValue(undefined);
|
||||||
|
render(<PluginDetailsPage pluginId="not-exist" />);
|
||||||
|
expect(screen.getByText('Plugin not found')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show angular deprecation notice when angular is detected', () => {
|
||||||
|
mockUseGetSingle.mockReturnValue({ ...plugin, angularDetected: true });
|
||||||
|
render(<PluginDetailsPage pluginId="test-plugin" />);
|
||||||
|
expect(screen.getByText(/legacy platform based on AngularJS/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show right panel when feature toggle is disabled', () => {
|
||||||
|
config.featureToggles.pluginsDetailsRightPanel = false;
|
||||||
|
render(<PluginDetailsPage pluginId="test-plugin" />);
|
||||||
|
expect(screen.queryByTestId('plugin-details-panel')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show right panel when feature toggle is enabled and screen is wide', () => {
|
||||||
|
config.featureToggles.pluginsDetailsRightPanel = true;
|
||||||
|
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
||||||
|
matches: query !== '(max-width: 600px)',
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addEventListener: jest.fn(),
|
||||||
|
removeEventListener: jest.fn(),
|
||||||
|
dispatchEvent: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
render(<PluginDetailsPage pluginId="test-plugin" />);
|
||||||
|
expect(screen.getByTestId('plugin-details-panel')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show "Plugin details" tab when screen is narrow', () => {
|
||||||
|
config.featureToggles.pluginsDetailsRightPanel = true;
|
||||||
|
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
||||||
|
matches: query === '(max-width: 600px)',
|
||||||
|
media: query,
|
||||||
|
onchange: null,
|
||||||
|
addEventListener: jest.fn(),
|
||||||
|
removeEventListener: jest.fn(),
|
||||||
|
dispatchEvent: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
render(<PluginDetailsPage pluginId="test-plugin" />);
|
||||||
|
expect(screen.getByRole('tab', { name: 'Plugin details' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useLocation } from 'react-router-dom-v5-compat';
|
import { useLocation } from 'react-router-dom-v5-compat';
|
||||||
|
import { useMedia } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
@ -12,7 +13,7 @@ import { AngularDeprecationPluginNotice } from '../../angularDeprecation/Angular
|
|||||||
import { Loader } from '../components/Loader';
|
import { Loader } from '../components/Loader';
|
||||||
import { PluginDetailsBody } from '../components/PluginDetailsBody';
|
import { PluginDetailsBody } from '../components/PluginDetailsBody';
|
||||||
import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
|
import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
|
||||||
import { PluginDetailsRightPanel } from '../components/PluginDetailsRightPanel';
|
import { PluginDetailsPanel } from '../components/PluginDetailsPanel';
|
||||||
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
|
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
|
||||||
import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs';
|
import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs';
|
||||||
import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions';
|
import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions';
|
||||||
@ -45,7 +46,12 @@ export function PluginDetailsPage({
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance
|
const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance
|
||||||
const { navModel, activePageId } = usePluginDetailsTabs(plugin, queryParams.get('page') as PluginTabIds);
|
const isNarrowScreen = useMedia('(max-width: 600px)');
|
||||||
|
const { navModel, activePageId } = usePluginDetailsTabs(
|
||||||
|
plugin,
|
||||||
|
queryParams.get('page') as PluginTabIds,
|
||||||
|
isNarrowScreen
|
||||||
|
);
|
||||||
const { actions, info, subtitle } = usePluginPageExtensions(plugin);
|
const { actions, info, subtitle } = usePluginPageExtensions(plugin);
|
||||||
const { isLoading: isFetchLoading } = useFetchStatus();
|
const { isLoading: isFetchLoading } = useFetchStatus();
|
||||||
const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus();
|
const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus();
|
||||||
@ -93,10 +99,18 @@ export function PluginDetailsPage({
|
|||||||
<PluginDetailsSignature plugin={plugin} className={styles.alert} />
|
<PluginDetailsSignature plugin={plugin} className={styles.alert} />
|
||||||
<PluginDetailsDisabledError plugin={plugin} className={styles.alert} />
|
<PluginDetailsDisabledError plugin={plugin} className={styles.alert} />
|
||||||
<PluginDetailsDeprecatedWarning plugin={plugin} className={styles.alert} />
|
<PluginDetailsDeprecatedWarning plugin={plugin} className={styles.alert} />
|
||||||
<PluginDetailsBody queryParams={Object.fromEntries(queryParams)} plugin={plugin} pageId={activePageId} />
|
<PluginDetailsBody
|
||||||
|
queryParams={Object.fromEntries(queryParams)}
|
||||||
|
plugin={plugin}
|
||||||
|
pageId={activePageId}
|
||||||
|
info={info}
|
||||||
|
showDetails={isNarrowScreen}
|
||||||
|
/>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</Page.Contents>
|
</Page.Contents>
|
||||||
{config.featureToggles.pluginsDetailsRightPanel && <PluginDetailsRightPanel info={info} plugin={plugin} />}
|
{!isNarrowScreen && config.featureToggles.pluginsDetailsRightPanel && (
|
||||||
|
<PluginDetailsPanel pluginExtentionsInfo={info} plugin={plugin} />
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
import { render, screen } from 'test/test-utils';
|
||||||
|
|
||||||
|
import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
|
||||||
|
|
||||||
|
import { CatalogPlugin } from '../types';
|
||||||
|
|
||||||
|
import { PluginDetailsPanel } from './PluginDetailsPanel';
|
||||||
|
|
||||||
|
const mockPlugin: CatalogPlugin = {
|
||||||
|
description: 'Test plugin description',
|
||||||
|
downloads: 1000,
|
||||||
|
hasUpdate: false,
|
||||||
|
id: 'test-plugin',
|
||||||
|
info: {
|
||||||
|
logos: {
|
||||||
|
small: 'small-logo-url',
|
||||||
|
large: 'large-logo-url',
|
||||||
|
},
|
||||||
|
keywords: ['test', 'plugin'],
|
||||||
|
},
|
||||||
|
isDev: false,
|
||||||
|
isCore: false,
|
||||||
|
isEnterprise: false,
|
||||||
|
isInstalled: true,
|
||||||
|
isDisabled: false,
|
||||||
|
isDeprecated: false,
|
||||||
|
isManaged: false,
|
||||||
|
isPreinstalled: { found: false, withVersion: false },
|
||||||
|
isPublished: true,
|
||||||
|
name: 'Test Plugin',
|
||||||
|
orgName: 'Test Org',
|
||||||
|
signature: PluginSignatureStatus.valid,
|
||||||
|
signatureType: PluginSignatureType.grafana,
|
||||||
|
signatureOrg: 'Test Signature Org',
|
||||||
|
popularity: 4,
|
||||||
|
publishedAt: '2023-01-01',
|
||||||
|
type: PluginType.app,
|
||||||
|
updatedAt: '2023-12-01',
|
||||||
|
installedVersion: '1.0.0',
|
||||||
|
latestVersion: '1.1.0',
|
||||||
|
details: {
|
||||||
|
readme: 'Test readme',
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
version: '1.0.0',
|
||||||
|
createdAt: '2023-01-01',
|
||||||
|
isCompatible: true,
|
||||||
|
grafanaDependency: '>=9.0.0',
|
||||||
|
angularDetected: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
name: 'Website',
|
||||||
|
url: 'https://test-plugin.com',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
grafanaDependency: '>=9.0.0',
|
||||||
|
statusContext: 'stable',
|
||||||
|
},
|
||||||
|
angularDetected: false,
|
||||||
|
isFullyInstalled: true,
|
||||||
|
accessControl: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockInfo = [
|
||||||
|
{ label: 'Version', value: '1.1.0' },
|
||||||
|
{ label: 'Author', value: 'Test Author' },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('PluginDetailsPanel', () => {
|
||||||
|
it('should render installed version when plugin is installed', () => {
|
||||||
|
render(<PluginDetailsPanel plugin={mockPlugin} pluginExtentionsInfo={mockInfo} />);
|
||||||
|
const installedVersionLabel = screen.getByText('Installed version:');
|
||||||
|
// Get the version text that's next to the label
|
||||||
|
const installedVersion = installedVersionLabel.nextElementSibling;
|
||||||
|
expect(installedVersionLabel).toBeInTheDocument();
|
||||||
|
expect(installedVersion).toHaveTextContent('1.0.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render latest version information', () => {
|
||||||
|
render(<PluginDetailsPanel plugin={mockPlugin} pluginExtentionsInfo={mockInfo} />);
|
||||||
|
expect(screen.getByText('Latest version:')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('1.1.0')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render links section when plugin has links', () => {
|
||||||
|
render(<PluginDetailsPanel plugin={mockPlugin} pluginExtentionsInfo={mockInfo} />);
|
||||||
|
const link = screen.getByText('Website');
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
expect(link).toHaveAttribute('href', 'https://test-plugin.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render links section when plugin has no links', () => {
|
||||||
|
const pluginWithoutLinks = {
|
||||||
|
...mockPlugin,
|
||||||
|
details: { ...mockPlugin.details, links: [] },
|
||||||
|
};
|
||||||
|
render(<PluginDetailsPanel plugin={pluginWithoutLinks} pluginExtentionsInfo={mockInfo} />);
|
||||||
|
expect(screen.queryByText('Links')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Website')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render report abuse section for non-core plugins', () => {
|
||||||
|
render(<PluginDetailsPanel plugin={mockPlugin} pluginExtentionsInfo={mockInfo} />);
|
||||||
|
expect(screen.getByText('Report a concern')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Contact Grafana Labs')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render report abuse section for core plugins', () => {
|
||||||
|
const corePlugin = { ...mockPlugin, isCore: true };
|
||||||
|
render(<PluginDetailsPanel plugin={corePlugin} pluginExtentionsInfo={mockInfo} />);
|
||||||
|
expect(screen.queryByText('Report a concern')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect custom width prop', () => {
|
||||||
|
render(<PluginDetailsPanel plugin={mockPlugin} pluginExtentionsInfo={mockInfo} width="300px" />);
|
||||||
|
const panel = screen.getByTestId('plugin-details-panel');
|
||||||
|
expect(panel).toHaveStyle({ maxWidth: '300px' });
|
||||||
|
});
|
||||||
|
});
|
@ -10,15 +10,17 @@ import { getLatestCompatibleVersion } from '../helpers';
|
|||||||
import { CatalogPlugin } from '../types';
|
import { CatalogPlugin } from '../types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
info: PageInfoItem[];
|
pluginExtentionsInfo: PageInfoItem[];
|
||||||
plugin: CatalogPlugin;
|
plugin: CatalogPlugin;
|
||||||
|
width?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PluginDetailsRightPanel(props: Props): React.ReactElement | null {
|
export function PluginDetailsPanel(props: Props): React.ReactElement | null {
|
||||||
const { info, plugin } = props;
|
const { pluginExtentionsInfo, plugin, width = '250px' } = props;
|
||||||
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
return (
|
return (
|
||||||
<Stack direction="column" gap={3} shrink={0} grow={0} maxWidth={'250px'}>
|
<Stack direction="column" gap={3} shrink={0} grow={0} maxWidth={width} data-testid="plugin-details-panel">
|
||||||
<Box padding={2} borderColor="medium" borderStyle="solid">
|
<Box padding={2} borderColor="medium" borderStyle="solid">
|
||||||
<Stack direction="column" gap={2}>
|
<Stack direction="column" gap={2}>
|
||||||
{plugin.isInstalled && plugin.installedVersion && (
|
{plugin.isInstalled && plugin.installedVersion && (
|
||||||
@ -29,18 +31,17 @@ export function PluginDetailsRightPanel(props: Props): React.ReactElement | null
|
|||||||
<div className={styles.pluginVersionDetails}>{plugin.installedVersion}</div>
|
<div className={styles.pluginVersionDetails}>{plugin.installedVersion}</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{info.map((infoItem, index) => {
|
<Stack wrap direction="column" gap={0.5}>
|
||||||
|
<Text color="secondary">
|
||||||
|
<Trans i18nKey="plugins.details.labels.latestVersion">Latest version: </Trans>
|
||||||
|
</Text>
|
||||||
|
<div className={styles.pluginVersionDetails}>
|
||||||
|
{plugin.latestVersion || getLatestCompatibleVersion(plugin.details?.versions)?.version}
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
{pluginExtentionsInfo.map((infoItem, index) => {
|
||||||
if (infoItem.label === 'Version') {
|
if (infoItem.label === 'Version') {
|
||||||
return (
|
return null;
|
||||||
<Stack key={index} wrap direction="column" gap={0.5}>
|
|
||||||
<Text color="secondary">
|
|
||||||
<Trans i18nKey="plugins.details.labels.latestVersion">Latest version: </Trans>
|
|
||||||
</Text>
|
|
||||||
<div className={styles.pluginVersionDetails}>
|
|
||||||
{getLatestCompatibleVersion(plugin.details?.versions)?.version}
|
|
||||||
</div>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack key={index} wrap direction="column" gap={0.5}>
|
<Stack key={index} wrap direction="column" gap={0.5}>
|
@ -16,13 +16,29 @@ type ReturnType = {
|
|||||||
activePageId: PluginTabIds | string;
|
activePageId: PluginTabIds | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usePluginDetailsTabs = (plugin?: CatalogPlugin, pageId?: PluginTabIds): ReturnType => {
|
function getCurrentPageId(
|
||||||
|
pageId: PluginTabIds | undefined,
|
||||||
|
isNarrowScreen: boolean | undefined,
|
||||||
|
defaultTab: string
|
||||||
|
): PluginTabIds | string {
|
||||||
|
if (!isNarrowScreen && pageId === PluginTabIds.PLUGINDETAILS) {
|
||||||
|
return defaultTab;
|
||||||
|
}
|
||||||
|
return pageId || defaultTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePluginDetailsTabs = (
|
||||||
|
plugin?: CatalogPlugin,
|
||||||
|
pageId?: PluginTabIds,
|
||||||
|
isNarrowScreen?: boolean
|
||||||
|
): ReturnType => {
|
||||||
const { loading, error, value: pluginConfig } = usePluginConfig(plugin);
|
const { loading, error, value: pluginConfig } = usePluginConfig(plugin);
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const defaultTab = useDefaultPage(plugin, pluginConfig);
|
const defaultTab = useDefaultPage(plugin, pluginConfig);
|
||||||
const isPublished = Boolean(plugin?.isPublished);
|
const isPublished = Boolean(plugin?.isPublished);
|
||||||
|
|
||||||
const currentPageId = pageId || defaultTab;
|
const currentPageId = getCurrentPageId(pageId, isNarrowScreen, defaultTab);
|
||||||
|
|
||||||
const navModelChildren = useMemo(() => {
|
const navModelChildren = useMemo(() => {
|
||||||
const canConfigurePlugins = plugin && contextSrv.hasPermissionInMetadata(AccessControlAction.PluginsWrite, plugin);
|
const canConfigurePlugins = plugin && contextSrv.hasPermissionInMetadata(AccessControlAction.PluginsWrite, plugin);
|
||||||
const navModelChildren: NavModelItem[] = [];
|
const navModelChildren: NavModelItem[] = [];
|
||||||
@ -45,6 +61,16 @@ export const usePluginDetailsTabs = (plugin?: CatalogPlugin, pageId?: PluginTabI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPublished && isNarrowScreen && config.featureToggles.pluginsDetailsRightPanel) {
|
||||||
|
navModelChildren.push({
|
||||||
|
text: PluginTabLabels.PLUGINDETAILS,
|
||||||
|
id: PluginTabIds.PLUGINDETAILS,
|
||||||
|
icon: 'info-circle',
|
||||||
|
url: `${pathname}?page=${PluginTabIds.PLUGINDETAILS}`,
|
||||||
|
active: PluginTabIds.PLUGINDETAILS === currentPageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Not extending the tabs with the config pages if the plugin is not installed
|
// Not extending the tabs with the config pages if the plugin is not installed
|
||||||
if (!pluginConfig) {
|
if (!pluginConfig) {
|
||||||
return navModelChildren;
|
return navModelChildren;
|
||||||
@ -112,7 +138,7 @@ export const usePluginDetailsTabs = (plugin?: CatalogPlugin, pageId?: PluginTabI
|
|||||||
}
|
}
|
||||||
|
|
||||||
return navModelChildren;
|
return navModelChildren;
|
||||||
}, [plugin, pluginConfig, pathname, isPublished, currentPageId]);
|
}, [plugin, pluginConfig, pathname, isPublished, currentPageId, isNarrowScreen]);
|
||||||
|
|
||||||
const navModel: NavModelItem = {
|
const navModel: NavModelItem = {
|
||||||
text: plugin?.name ?? '',
|
text: plugin?.name ?? '',
|
||||||
|
@ -256,6 +256,7 @@ export enum PluginTabLabels {
|
|||||||
USAGE = 'Usage',
|
USAGE = 'Usage',
|
||||||
IAM = 'IAM',
|
IAM = 'IAM',
|
||||||
CHANGELOG = 'Changelog',
|
CHANGELOG = 'Changelog',
|
||||||
|
PLUGINDETAILS = 'Plugin details',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PluginTabIds {
|
export enum PluginTabIds {
|
||||||
@ -266,6 +267,7 @@ export enum PluginTabIds {
|
|||||||
USAGE = 'usage',
|
USAGE = 'usage',
|
||||||
IAM = 'iam',
|
IAM = 'iam',
|
||||||
CHANGELOG = 'changelog',
|
CHANGELOG = 'changelog',
|
||||||
|
PLUGINDETAILS = 'right-panel',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RequestStatus {
|
export enum RequestStatus {
|
||||||
|
Loading…
Reference in New Issue
Block a user