Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions frontend/@types/console/window.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ declare interface Window {
GOOS: string;
graphqlBaseURL: string;
developerCatalogCategories: string;
/** JSON encoded configuration for the console's perspectives override */
perspectives: string;
developerCatalogTypes: string;
userSettingsLocation: string;
Expand Down Expand Up @@ -78,9 +79,9 @@ declare interface Window {
store?: {};
/** Console plugin store, only available in development builds for debugging */
pluginStore?: {};
/** Console legacy plugin entry callback, used to load dynamic plugins */
/** Console legacy plugin entry callback for loading dynamic plugins (4.21 and older) */
loadPluginEntry?: Function;
/** Console plugin entry callback, used to load dynamic plugins */
/** Console plugin entry callback for loading dynamic plugins (4.22 and newer) */
__load_plugin_entry__?: Function;
/** webpack shared scope object */
webpackSharedScope?: {};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import type { SetFeatureFlag } from '@console/dynamic-plugin-sdk';
import type { Perspective } from '@console/shared/src/hooks/usePerspectives';
import { hasReviewAccess } from '@console/shared/src/hooks/usePerspectives';
import {
hasReviewAccess,
PerspectiveVisibilityState,
} from '@console/shared/src/hooks/usePerspectives';
overridePerspectives,
} from '@console/shared/src/utils/override-perspectives';
import { FLAG_DEVELOPER_PERSPECTIVE } from '../../consts';

export const useDeveloperPerspectiveStateProvider = (setFeatureFlag: SetFeatureFlag) => {
if (!window.SERVER_FLAGS.perspectives) {
if (!overridePerspectives) {
setFeatureFlag(FLAG_DEVELOPER_PERSPECTIVE, true);
} else {
const perspectives: Perspective[] = JSON.parse(window.SERVER_FLAGS.perspectives);
const devPerspective = perspectives?.find((p) => p.id === 'dev');
const devPerspective = overridePerspectives.find((p) => p.id === 'dev');
if (!devPerspective) {
setFeatureFlag(FLAG_DEVELOPER_PERSPECTIVE, true);
} else if (devPerspective.visibility.state === PerspectiveVisibilityState.Disabled) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type {
PluginCustomProperties,
PluginLoaderInterface,
Extension,
LoadedExtension,
PluginManifest,
LocalPluginManifest,
RemotePluginManifest,
PluginRuntimeMetadata,
PluginLoaderInterface,
} from '@openshift/dynamic-plugin-sdk';
import { TestPluginStore } from '@openshift/dynamic-plugin-sdk';

Expand All @@ -16,10 +20,18 @@ const noopPluginLoader: PluginLoaderInterface = {
}),
};

type PluginManifestOptions = {
version?: string;
customProperties?: PluginCustomProperties;
};
type PluginManifestOptions = Partial<Pick<PluginRuntimeMetadata, 'version' | 'customProperties'>>;

export const createLocalPluginManifest = (
name: string,
options: PluginManifestOptions = {},
): LocalPluginManifest => ({
name,
version: options.version ?? '1.0.0',
extensions: [],
registrationMethod: 'local',
customProperties: options.customProperties,
});

export const createRemotePluginManifest = (
name: string,
Expand All @@ -45,27 +57,46 @@ export const createTestPluginStore = (
return pluginStore;
};

const getLoadedExtensions = <T extends Extension>(
pluginName: string,
extensions: T[],
): LoadedExtension<T>[] =>
extensions.map((e, index) => ({
...e,
pluginName,
uid: `${pluginName}[${index}]`,
}));

export const addLoadedPluginFromManifest = (
store: TestPluginStore,
manifest: PluginManifest,
extensions: Extension[],
enablePlugin = true,
) => {
store.addLoadedPlugin(manifest, getLoadedExtensions(manifest.name, extensions));

if (enablePlugin) {
store.enablePlugins([manifest.name]);
}
};

export const addLoadedPlugin = (
store: TestPluginStore,
name: string,
options: PluginManifestOptions = {},
): void => {
store.addLoadedPlugin(createRemotePluginManifest(name, options), []);
) => {
addLoadedPluginFromManifest(store, createRemotePluginManifest(name, options), []);
};

export const addPendingPlugin = (store: TestPluginStore, name: string): void => {
export const addPendingPlugin = (store: TestPluginStore, name: string) => {
store.addPendingPlugin(createRemotePluginManifest(name));
};

export const addFailedPlugin = (
store: TestPluginStore,
name: string,
errorMessage: string,
): void => {
export const addFailedPlugin = (store: TestPluginStore, name: string, errorMessage: string) => {
store.addFailedPlugin(createRemotePluginManifest(name), errorMessage);
};

export const addLoadedPluginWithoutVersion = (store: TestPluginStore, name: string): void => {
export const addLoadedPluginWithoutVersion = (store: TestPluginStore, name: string) => {
const manifest = createRemotePluginManifest(name);
delete (manifest as { version?: string }).version;
store.addLoadedPlugin(manifest, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import {
} from '@patternfly/react-core';
import { safeDump } from 'js-yaml';
import { useTranslation } from 'react-i18next';
import type {
Perspective as PerspectiveExtension,
AccessReviewResourceAttributes,
} from '@console/dynamic-plugin-sdk/src';
import type { Perspective as PerspectiveExtension } from '@console/dynamic-plugin-sdk/src';
import { isPerspective } from '@console/dynamic-plugin-sdk/src';
import type { K8sResourceKind } from '@console/internal/module/k8s';
import { useExtensions } from '@console/plugin-sdk/src/api/useExtensions';
Expand All @@ -29,27 +26,11 @@ import { SaveStatus } from '@console/shared/src/components/cluster-configuration
import { useConsoleOperatorConfig } from '@console/shared/src/components/cluster-configuration/useConsoleOperatorConfig';
import { useDebounceCallback } from '@console/shared/src/hooks/useDebounceCallback';
import { useTelemetry } from '@console/shared/src/hooks/useTelemetry';

enum PerspectiveVisibilityState {
Enabled = 'Enabled',
Disabled = 'Disabled',
AccessReview = 'AccessReview',
}

type PerspectiveAccessReview = {
required?: AccessReviewResourceAttributes[];
missing?: AccessReviewResourceAttributes[];
};

type PerspectiveVisibility = {
state: PerspectiveVisibilityState;
accessReview?: PerspectiveAccessReview;
};

type Perspective = {
id: string;
visibility: PerspectiveVisibility;
};
import type {
PerspectiveVisibility,
Perspective,
} from '@console/shared/src/utils/override-perspectives';
import { PerspectiveVisibilityState } from '@console/shared/src/utils/override-perspectives';

type PerspectivesConsoleConfig = K8sResourceKind & {
spec: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ import { render, waitFor } from '@testing-library/react';
import { useLocation } from 'react-router';
import type { Perspective } from '@console/dynamic-plugin-sdk';
import type { LoadedExtension } from '@console/dynamic-plugin-sdk/src/types';
import {
usePerspectives,
PerspectiveVisibilityState,
} from '@console/shared/src/hooks/usePerspectives';
import type { Perspective as PerspectiveType } from '@console/shared/src/hooks/usePerspectives';
import { usePerspectives } from '@console/shared/src/hooks/usePerspectives';
import type { Perspective as PerspectiveType } from '@console/shared/src/utils/override-perspectives';
import { PerspectiveVisibilityState } from '@console/shared/src/utils/override-perspectives';
import PerspectiveDetector from '../PerspectiveDetector';

let mockOverridePerspectives: PerspectiveType[] | undefined;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

jest.mock('@console/shared/src/utils/override-perspectives', () => ({
...jest.requireActual('@console/shared/src/utils/override-perspectives'),
get overridePerspectives() {
return mockOverridePerspectives;
},
}));

jest.mock('@console/shared/src/hooks/usePerspectives', () => ({
...jest.requireActual('@console/shared/src/hooks/usePerspectives'),
usePerspectives: jest.fn(),
Expand Down Expand Up @@ -94,7 +101,7 @@ describe('PerspectiveDetector', () => {
});

it('should set admin as default perspective when all perspectives are disabled', async () => {
const perspectives: PerspectiveType[] = [
mockOverridePerspectives = [
{
id: 'dev',
visibility: {
Expand Down Expand Up @@ -122,7 +129,6 @@ describe('PerspectiveDetector', () => {
},
},
];
window.SERVER_FLAGS.perspectives = JSON.stringify(perspectives);

let promiseResolver: (value: () => [boolean, boolean]) => void;
const testPromise = new Promise<() => [boolean, boolean]>(
Expand Down
69 changes: 46 additions & 23 deletions frontend/packages/console-app/src/components/nav/NavHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import type { FC, MouseEvent, Ref } from 'react';
import { useMemo, useState, useCallback } from 'react';
import type { MenuToggleElement } from '@patternfly/react-core';
import { MenuToggle, Select, SelectList, SelectOption, Title } from '@patternfly/react-core';
import { RhUiGearGroupFillIcon } from '@patternfly/react-icons';
import { RhUiGearGroupFillIcon, RhUiInProgressIcon } from '@patternfly/react-icons';
import { useTranslation } from 'react-i18next';
import type { Perspective } from '@console/dynamic-plugin-sdk';
import { useActivePerspective } from '@console/dynamic-plugin-sdk';
import { AsyncComponent } from '@console/internal/components/utils/async';
import { usePluginInfo } from '@console/plugin-sdk/src/api/usePluginInfo';
import { usePerspectives } from '@console/shared/src/hooks/usePerspectives';
import {
PerspectiveVisibilityState,
overridePerspectives,
} from '@console/shared/src/utils/override-perspectives';

type NavHeaderProps = {
onPerspectiveSelected: () => void;
Expand Down Expand Up @@ -50,6 +55,7 @@ const NavHeader: FC<NavHeaderProps> = ({ onPerspectiveSelected }) => {
const [activePerspective, setActivePerspective] = useActivePerspective();
const [isPerspectiveDropdownOpen, setPerspectiveDropdownOpen] = useState(false);
const perspectiveExtensions = usePerspectives();
const pluginInfoEntries = usePluginInfo();
const { t } = useTranslation('console-app');

const togglePerspectiveOpen = useCallback(() => {
Expand All @@ -74,11 +80,35 @@ const NavHeader: FC<NavHeaderProps> = ({ onPerspectiveSelected }) => {
/>
));

const { icon, name } = useMemo(
const { icon, name } = useMemo<{
icon: Perspective['properties']['icon'];
name: Perspective['properties']['name'];
}>(
() =>
perspectiveExtensions.find((p) => p?.properties?.id === activePerspective)?.properties ??
perspectiveExtensions[0]?.properties ?? { icon: null, name: null },
[activePerspective, perspectiveExtensions],
perspectiveExtensions[0]?.properties ?? { icon: null, name: t('Core platform') },
[activePerspective, perspectiveExtensions, t],
);

const ActivePerspectiveIcon = icon ? (
<AsyncComponent
loader={() => icon().then((m) => m.default)}
LoadingComponent={IconLoadingComponent}
/>
) : (
<RhUiGearGroupFillIcon /> // Default icon for admin perspective
);

const allPluginsProcessed = useMemo(
() => pluginInfoEntries.every((i) => i.status !== 'pending'),
[pluginInfoEntries],
);

const activePerspectiveDisabled = useMemo(
() =>
overridePerspectives?.find((p) => p.id === activePerspective)?.visibility?.state ===
PerspectiveVisibilityState.Disabled,
[activePerspective],
);

return perspectiveDropdownItems.length > 1 ? (
Expand All @@ -99,20 +129,11 @@ const NavHeader: FC<NavHeaderProps> = ({ onPerspectiveSelected }) => {
isExpanded={isPerspectiveDropdownOpen}
ref={toggleRef}
onClick={() => togglePerspectiveOpen()}
icon={
icon && (
<AsyncComponent
loader={() => icon().then((m) => m.default)}
LoadingComponent={IconLoadingComponent}
/>
)
}
icon={ActivePerspectiveIcon}
>
{name && (
<Title headingLevel="h2" size="md">
{name}
</Title>
)}
<Title headingLevel="h2" size="md">
{name}
</Title>
</MenuToggle>
)}
popperProps={{
Expand All @@ -123,13 +144,15 @@ const NavHeader: FC<NavHeaderProps> = ({ onPerspectiveSelected }) => {
</Select>
</div>
) : (
<div
data-test="perspective-switcher-toggle"
data-test-id="perspective-switcher-toggle"
id="core-platform-perspective"
>
<div data-test="perspective-switcher-toggle" data-test-id="perspective-switcher-toggle">
<Title headingLevel="h2" size="md">
<RhUiGearGroupFillIcon /> {t('Core platform')}
{!allPluginsProcessed && activePerspective === 'admin' && activePerspectiveDisabled ? (
<RhUiInProgressIcon data-test="perspective-progress-icon" />
) : (
<>
{ActivePerspectiveIcon} {name}
</>
)}
</Title>
</div>
);
Expand Down
Loading