-
Notifications
You must be signed in to change notification settings - Fork 16
/
Navigation.ts
141 lines (123 loc) · 5.01 KB
/
Navigation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import * as React from 'react';
import { getActivePlugins } from './Utils/plugins';
import { Extension, PluginManifest } from '@openshift/dynamic-plugin-sdk';
import { HrefNavItem, NavSection, isNavSection } from '@openshift/dynamic-plugin-sdk-extensions';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
import packageInfo from '../package.json';
export type EnabledPlugin = {
name: string;
pathPrefix?: string;
};
export interface RouteProps {
isHidden?: boolean;
appId?: string;
href?: string;
title?: string;
}
export interface DynamicNav {
dynamicNav: string;
base?: string;
isBeta?: () => boolean;
}
const navExtensionTypes = ['console.navigation/href', 'console.navigation/section', 'core.navigation/href', 'core.navigation/section'];
type NavExtension = HrefNavItem | NavSection;
export type GetAllExtensions = (base?: string, isBeta?: () => boolean) => Promise<NavExtension[]>;
export type CalculateRoutes = (navIdentifier: [string, string], extensions: (HrefNavItem | NavSection)[]) => RouteProps[];
export type Navigation = {
expandable: boolean;
title: string;
routes: RouteProps[];
};
const isFulfilledPromise = (result: PromiseSettledResult<Extension[]>): result is PromiseFulfilledResult<Extension[]> => {
return result.status === 'fulfilled' && Boolean(result.value);
};
const isNavItem = (extension: Extension): extension is NavExtension => {
return navExtensionTypes.includes(extension.type);
};
const getAllExtensions: GetAllExtensions = async (base = '', isBeta = () => false) => {
const plugins = await getActivePlugins(isBeta(), packageInfo.insights.appname);
const results: PromiseSettledResult<Extension[]>[] = await Promise.allSettled(
plugins.flatMap(async ({ name: pluginName, pathPrefix = '/api/plugins' }: EnabledPlugin) => {
const url = `${base}${pathPrefix}/${pluginName}/plugin-manifest.json`;
const response: Response = await fetch(url);
if (response.status !== 200) {
const msg = `${url} - ${response.status} - ${response.statusText}`;
// eslint-disable-next-line no-console
console.error(msg);
throw new Error(msg);
}
const manifest: PluginManifest = await response.json();
return manifest.extensions;
}),
);
return results
.filter(isFulfilledPromise)
.map(({ value }) => value)
.flat()
.filter(isNavItem);
};
const isNavigation = (extension: NavExtension): extension is HrefNavItem =>
['console.navigation/href', 'core.navigation/href'].includes(extension.type);
const isCurrNavigation = (array: HrefNavItem[], idx: number, currHref: string): boolean =>
array.findIndex(({ properties: { href } }: HrefNavItem) => href === currHref) === idx;
const calculateRoutes: CalculateRoutes = ([appId, navSection], extensions) => {
return extensions
.filter(isNavigation)
.filter(({ properties: { section, href } }, idx, array) => section === navSection && isCurrNavigation(array, idx, href))
.map(({ properties: { name, href } }) => ({
appId,
href: `${navSection ? `/${navSection}` : ''}${href}`,
title: name,
}));
};
const calculateNavigation = async ({ dynamicNav, base, isBeta }: DynamicNav): Promise<Navigation | RouteProps[]> => {
const [appId, navSection] = dynamicNav.split('/');
let allExtensions: NavExtension[] = [];
let routes: RouteProps | RouteProps[] = [];
try {
allExtensions = await getAllExtensions(base, isBeta);
routes = calculateRoutes([appId, navSection], allExtensions);
if (routes.length === 0) {
routes = [{ isHidden: true }];
}
} catch (e) {
routes = [{ isHidden: true }];
// eslint-disable-next-line no-console
console.error('Problem fetching extensions', e);
}
const { properties: currSection } = allExtensions.find((extension: NavExtension) => isNavSection(extension)) || ({} as NavExtension);
return currSection
? ({
expandable: true,
title: currSection.name,
routes,
} as Navigation)
: routes;
};
/**
* Hook to be used by chrome to calculate navigation chunks.
* @param { dynamicNav, currentNamespace }
* * dynamicNav - entry idicating dynamic navigation
* * currentNamespace - current app namespace
* @returns either navigation object for nested items or array of navigation object for multiple entries.
*/
export const useNavigation = ({ dynamicNav }: DynamicNav): Navigation | RouteProps[] | undefined => {
const [navigation, setNavigation] = React.useState<Navigation | RouteProps[]>();
const unmounted = React.useRef<boolean>(false);
const { isBeta } = useChrome();
React.useEffect(() => {
if (dynamicNav) {
// this is just one off for now, but we can start building on this
calculateNavigation({ dynamicNav, base: isBeta() ? `/beta` : '', isBeta }).then((data: Navigation | RouteProps[]) => {
if (!unmounted.current) {
setNavigation(data);
}
});
}
return () => {
unmounted.current = true;
};
}, [dynamicNav, isBeta]);
return navigation;
};
export default calculateNavigation;