From a995d4dcfcd6ec86414ae84794f6f30694c1734f Mon Sep 17 00:00:00 2001 From: Rohit Rai Date: Tue, 7 Jul 2020 23:28:47 +0530 Subject: [PATCH] Add support for perspective detection using extensions --- frontend/__tests__/reducers/ui.spec.ts | 14 ---- .../detect-perspective/DetectPerspective.tsx | 77 +++++++++++++++++++ .../src/typings/perspectives.ts | 2 + frontend/packages/dev-console/src/plugin.tsx | 2 + .../__tests__/usePerspectiveDetection.spec.ts | 35 +++++++++ .../src/utils/usePerspectiveDetection.ts | 13 ++++ frontend/public/components/app.jsx | 6 +- frontend/public/reducers/ui.ts | 7 -- 8 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 frontend/packages/console-app/src/components/detect-perspective/DetectPerspective.tsx create mode 100644 frontend/packages/dev-console/src/utils/__tests__/usePerspectiveDetection.spec.ts create mode 100644 frontend/packages/dev-console/src/utils/usePerspectiveDetection.ts diff --git a/frontend/__tests__/reducers/ui.spec.ts b/frontend/__tests__/reducers/ui.spec.ts index 536e472afc1d..cb7f31fa904c 100644 --- a/frontend/__tests__/reducers/ui.spec.ts +++ b/frontend/__tests__/reducers/ui.spec.ts @@ -17,20 +17,6 @@ describe('getDefaultPerspective', () => { expect(getDefaultPerspective()).toBeUndefined(); }); - it('should default to perspective extension marked default', () => { - // return Perspectives extension with one marked as the default - spyOn(pluginStore, 'getAllExtensions').and.returnValue([ - { - type: 'Perspective', - properties: { - id: 'admin', - default: true, - }, - } as Perspective, - ]); - expect(getDefaultPerspective()).toBe('admin'); - }); - it('should default to localStorage if perspective is a valid extension', () => { // return Perspectives extension whose id matches that in the localStorage spyOn(pluginStore, 'getAllExtensions').and.returnValue([ diff --git a/frontend/packages/console-app/src/components/detect-perspective/DetectPerspective.tsx b/frontend/packages/console-app/src/components/detect-perspective/DetectPerspective.tsx new file mode 100644 index 000000000000..8ab458ea3113 --- /dev/null +++ b/frontend/packages/console-app/src/components/detect-perspective/DetectPerspective.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import * as _ from 'lodash'; +import { connect, Dispatch } from 'react-redux'; +import { useExtensions, isPerspective } from '@console/plugin-sdk'; +import { getActivePerspective } from '@console/internal/reducers/ui'; +import { RootState } from '@console/internal/redux'; +import * as UIActions from '@console/internal/actions/ui'; + +type OwnProps = { + children: React.ReactNode; +}; + +type StateProps = { + activePerspective: string; +}; + +type DispatchProps = { + setActivePerspective: (string) => void; +}; + +type DetectPerspectiveProps = OwnProps & StateProps & DispatchProps; + +const DetectPerspective: React.FC = ({ + activePerspective, + children, + setActivePerspective, +}) => { + let detectedPerspective: string; + let detectionComplete: boolean; + const perspectiveExtensions = useExtensions(isPerspective); + const defaultPerspective = perspectiveExtensions.find((p) => p.properties.default); + const perspectiveDetectors = perspectiveExtensions.filter( + (p) => p.properties.usePerspectiveDetection, + ); + + perspectiveDetectors.some((p) => { + const isDetectedPerspective = p.properties.usePerspectiveDetection(); + detectionComplete = _.isBoolean(isDetectedPerspective); + if (isDetectedPerspective) { + detectedPerspective = p.properties.id; + return true; + } + return false; + }); + + React.useEffect(() => { + if (!activePerspective) { + if (detectedPerspective) { + setActivePerspective(detectedPerspective); + } else if (perspectiveDetectors.length < 1 || detectionComplete) { + setActivePerspective(defaultPerspective.properties.id); // set default perspective if there are no detectors or none of the detections were successfull + } + } + }, [ + activePerspective, + defaultPerspective, + detectedPerspective, + detectionComplete, + perspectiveDetectors.length, + setActivePerspective, + ]); + + return activePerspective ? <>{children} : null; +}; + +const mapStateToProps = (state: RootState) => ({ + activePerspective: getActivePerspective(state), +}); + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + setActivePerspective: (perspective) => dispatch(UIActions.setActivePerspective(perspective)), +}); + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(DetectPerspective); diff --git a/frontend/packages/console-plugin-sdk/src/typings/perspectives.ts b/frontend/packages/console-plugin-sdk/src/typings/perspectives.ts index d70e606b399c..670ad3c64760 100644 --- a/frontend/packages/console-plugin-sdk/src/typings/perspectives.ts +++ b/frontend/packages/console-plugin-sdk/src/typings/perspectives.ts @@ -20,6 +20,8 @@ namespace ExtensionProperties { getK8sLandingPageURL: GetLandingPage; /** The function to get redirect URL for import flow. */ getImportRedirectURL: (project: string) => string; + /** The hook to detect default perspective */ + usePerspectiveDetection?: () => boolean; // isDetectedPerspective } } diff --git a/frontend/packages/dev-console/src/plugin.tsx b/frontend/packages/dev-console/src/plugin.tsx index 2e03c2153dc2..45827a9d91d9 100755 --- a/frontend/packages/dev-console/src/plugin.tsx +++ b/frontend/packages/dev-console/src/plugin.tsx @@ -64,6 +64,7 @@ import { OperatorsTopologyConsumedExtensions, operatorsTopologyPlugin, } from './components/topology/operators/operatorsTopologyPlugin'; +import { usePerspectiveDetection } from './utils/usePerspectiveDetection'; const { ClusterTaskModel, @@ -422,6 +423,7 @@ const plugin: Plugin = [ getLandingPageURL: () => '/topology', getK8sLandingPageURL: () => '/add', getImportRedirectURL: (project) => `/topology/ns/${project}`, + usePerspectiveDetection, }, }, { diff --git a/frontend/packages/dev-console/src/utils/__tests__/usePerspectiveDetection.spec.ts b/frontend/packages/dev-console/src/utils/__tests__/usePerspectiveDetection.spec.ts new file mode 100644 index 000000000000..a71d5f318b5a --- /dev/null +++ b/frontend/packages/dev-console/src/utils/__tests__/usePerspectiveDetection.spec.ts @@ -0,0 +1,35 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore: FIXME missing exports due to out-of-sync @types/react-redux version +import { useSelector } from 'react-redux'; +import { testHook } from '@console/shared/src/test-utils/hooks-utils'; +import { usePerspectiveDetection } from '../usePerspectiveDetection'; + +jest.mock('react-redux', () => ({ + useSelector: jest.fn(), +})); + +describe('usePerspectiveDetection', () => { + it('should return null if CAN_GET_NS flag is pending', () => { + (useSelector as jest.Mock).mockImplementation(() => ({ + CAN_GET_NS: undefined, + })); + + testHook(() => { + const isDetectedPerspective = usePerspectiveDetection(); + + expect(isDetectedPerspective).toBe(null); + }); + }); + + it('should return true if CAN_GET_NS flag is false', () => { + (useSelector as jest.Mock).mockImplementation(() => ({ + CAN_GET_NS: false, + })); + + testHook(() => { + const isDetectedPerspective = usePerspectiveDetection(); + + expect(isDetectedPerspective).toBe(true); + }); + }); +}); diff --git a/frontend/packages/dev-console/src/utils/usePerspectiveDetection.ts b/frontend/packages/dev-console/src/utils/usePerspectiveDetection.ts new file mode 100644 index 000000000000..4f8eb5220dbb --- /dev/null +++ b/frontend/packages/dev-console/src/utils/usePerspectiveDetection.ts @@ -0,0 +1,13 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-ignore +// @ts-ignore: FIXME missing exports due to out-of-sync @types/react-redux version +import { useSelector } from 'react-redux'; +import { RootState } from '@console/internal/redux'; +import { getFlagsObject, flagPending } from '@console/internal/reducers/features'; + +export const usePerspectiveDetection = () => { + const flags = useSelector((state: RootState) => getFlagsObject(state)); + const canGetNS = flags.CAN_GET_NS; + const isDeveloper = !canGetNS; + + return flagPending(canGetNS) ? null : isDeveloper; +}; diff --git a/frontend/public/components/app.jsx b/frontend/public/components/app.jsx index 395c85b2abd0..35d89ae12753 100644 --- a/frontend/public/components/app.jsx +++ b/frontend/public/components/app.jsx @@ -20,6 +20,8 @@ import { receivedResources, watchAPIServices } from '../actions/k8s'; // cloud shell imports must come later than features import CloudShell from '@console/app/src/components/cloud-shell/CloudShell'; import CloudShellTab from '@console/app/src/components/cloud-shell/CloudShellTab'; +import DetectPerspective from '@console/app/src/components/detect-perspective/DetectPerspective'; + const consoleLoader = () => import( '@console/kubevirt-plugin/src/components/connected-vm-console/vm-console-page' /* webpackChunkName: "kubevirt" */ @@ -130,7 +132,7 @@ class App extends React.PureComponent { const { productName } = getBrandingDetails(); return ( - <> + - + ); } } diff --git a/frontend/public/reducers/ui.ts b/frontend/public/reducers/ui.ts index 7f95413aef0c..4347fc56d3d4 100644 --- a/frontend/public/reducers/ui.ts +++ b/frontend/public/reducers/ui.ts @@ -30,13 +30,6 @@ export function getDefaultPerspective() { // invalid saved perspective activePerspective = undefined; } - if (!activePerspective) { - // assign default perspective - const defaultPerspective = perspectiveExtensions.find((p) => p.properties.default); - if (defaultPerspective) { - activePerspective = defaultPerspective.properties.id; - } - } return activePerspective || undefined; }