@red-hat-developer-hub/backstage-plugin-quickstart 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/components/QuickstartDrawer.esm.js +14 -18
- package/dist/components/QuickstartDrawer.esm.js.map +1 -1
- package/dist/components/QuickstartDrawerProvider.esm.js +55 -13
- package/dist/components/QuickstartDrawerProvider.esm.js.map +1 -1
- package/dist/hooks/useQuickstartRole.esm.js +22 -5
- package/dist/hooks/useQuickstartRole.esm.js.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @red-hat-developer-hub/backstage-plugin-quickstart
|
|
2
2
|
|
|
3
|
+
## 1.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9057587: Fix Quickstart drawer re-opening on close by scoping drawer flags per user, caching resolved role per session, and filtering items only when the drawer is open; preserves first-time auto-open and respects manual close.
|
|
8
|
+
|
|
9
|
+
## 1.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- d49b252: Backstage version bump to v1.42.5
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- e8cc528: Updated dependency `@red-hat-developer-hub/backstage-plugin-theme` to `^0.10.0` and move it from plugin dependencies to devDependencies.
|
|
18
|
+
|
|
3
19
|
## 1.4.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -5,31 +5,27 @@ import { Quickstart } from './Quickstart.esm.js';
|
|
|
5
5
|
import { useQuickstartDrawerContext } from '../hooks/useQuickstartDrawerContext.esm.js';
|
|
6
6
|
import { filterQuickstartItemsByRole } from '../utils/filterQuickstartItems.esm.js';
|
|
7
7
|
import { useQuickstartRole } from '../hooks/useQuickstartRole.esm.js';
|
|
8
|
-
import {
|
|
8
|
+
import { useMemo } from 'react';
|
|
9
9
|
|
|
10
10
|
const QuickstartDrawer = () => {
|
|
11
|
-
const { isDrawerOpen, closeDrawer,
|
|
12
|
-
const hasAutoOpened = useRef(false);
|
|
11
|
+
const { isDrawerOpen, closeDrawer, drawerWidth } = useQuickstartDrawerContext();
|
|
13
12
|
const apiHolder = useApiHolder();
|
|
14
13
|
const config = apiHolder.get(configApiRef);
|
|
15
|
-
const quickstartItems =
|
|
14
|
+
const quickstartItems = useMemo(() => {
|
|
15
|
+
return config?.has("app.quickstart") ? config.get("app.quickstart") : [];
|
|
16
|
+
}, [config]);
|
|
16
17
|
const { isLoading, userRole } = useQuickstartRole();
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!isLoading && filteredItems.length === 0) {
|
|
25
|
-
if (isDrawerOpen) {
|
|
26
|
-
closeDrawer();
|
|
27
|
-
}
|
|
18
|
+
const eligibleItems = useMemo(() => {
|
|
19
|
+
return !isLoading && userRole ? filterQuickstartItemsByRole(quickstartItems, userRole) : [];
|
|
20
|
+
}, [isLoading, userRole, quickstartItems]);
|
|
21
|
+
const filteredItems = useMemo(() => {
|
|
22
|
+
return isDrawerOpen ? eligibleItems : [];
|
|
23
|
+
}, [isDrawerOpen, eligibleItems]);
|
|
24
|
+
if (quickstartItems.length === 0) {
|
|
28
25
|
return null;
|
|
29
26
|
}
|
|
30
|
-
if (isLoading &&
|
|
31
|
-
|
|
32
|
-
hasAutoOpened.current = false;
|
|
27
|
+
if (!isLoading && eligibleItems.length === 0) {
|
|
28
|
+
return null;
|
|
33
29
|
}
|
|
34
30
|
return /* @__PURE__ */ jsx(
|
|
35
31
|
Drawer,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuickstartDrawer.esm.js","sources":["../../src/components/QuickstartDrawer.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Drawer from '@mui/material/Drawer';\nimport { ThemeConfig } from '@red-hat-developer-hub/backstage-plugin-theme';\nimport { configApiRef, useApiHolder } from '@backstage/core-plugin-api';\nimport { Quickstart } from './Quickstart';\nimport { useQuickstartDrawerContext } from '../hooks/useQuickstartDrawerContext';\nimport { QuickstartItemData } from '../types';\nimport { filterQuickstartItemsByRole } from '../utils';\nimport { useQuickstartRole } from '../hooks/useQuickstartRole';\nimport {
|
|
1
|
+
{"version":3,"file":"QuickstartDrawer.esm.js","sources":["../../src/components/QuickstartDrawer.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Drawer from '@mui/material/Drawer';\nimport { ThemeConfig } from '@red-hat-developer-hub/backstage-plugin-theme';\nimport { configApiRef, useApiHolder } from '@backstage/core-plugin-api';\nimport { Quickstart } from './Quickstart';\nimport { useQuickstartDrawerContext } from '../hooks/useQuickstartDrawerContext';\nimport { QuickstartItemData } from '../types';\nimport { filterQuickstartItemsByRole } from '../utils';\nimport { useQuickstartRole } from '../hooks/useQuickstartRole';\nimport { useMemo } from 'react';\n\nexport const QuickstartDrawer = () => {\n const { isDrawerOpen, closeDrawer, drawerWidth } =\n useQuickstartDrawerContext();\n\n const apiHolder = useApiHolder();\n const config = apiHolder.get(configApiRef);\n const quickstartItems: QuickstartItemData[] = useMemo(() => {\n return config?.has('app.quickstart')\n ? (config.get('app.quickstart') as QuickstartItemData[])\n : [];\n }, [config]);\n\n const { isLoading, userRole } = useQuickstartRole();\n\n // Items available to the user based on cached/derived role\n const eligibleItems = useMemo(() => {\n return !isLoading && userRole\n ? filterQuickstartItemsByRole(quickstartItems, userRole)\n : [];\n }, [isLoading, userRole, quickstartItems]);\n\n // Only expose items to the body when drawer is open to avoid re-renders during close\n const filteredItems = useMemo(() => {\n return isDrawerOpen ? eligibleItems : [];\n }, [isDrawerOpen, eligibleItems]);\n\n // No auto-open logic here; the provider initializes per user (visited/open)\n\n // If no quickstart items are configured at all, don't render the drawer to avoid reserving space\n if (quickstartItems.length === 0) {\n return null;\n }\n\n // If there are no items for the user, hide the drawer entirely\n if (!isLoading && eligibleItems.length === 0) {\n return null;\n }\n\n // No role-fetching or filtering here when the drawer is closed\n\n return (\n <Drawer\n sx={{\n '& .v5-MuiDrawer-paper': {\n width: drawerWidth,\n boxSizing: 'border-box',\n backgroundColor: theme =>\n `${\n (theme as ThemeConfig).palette?.rhdh?.general\n .sidebarBackgroundColor\n }`,\n justifyContent: 'space-between',\n },\n // Only apply header offset when global header exists\n 'body:has(#global-header) &': {\n '& .v5-MuiDrawer-paper': {\n top: '64px !important',\n height: 'calc(100vh - 64px) !important',\n },\n },\n }}\n variant=\"persistent\"\n anchor=\"right\"\n open={isDrawerOpen}\n >\n <Quickstart\n quickstartItems={filteredItems}\n handleDrawerClose={closeDrawer}\n isLoading={isLoading}\n />\n </Drawer>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AA0BO,MAAM,mBAAmB,MAAM;AACpC,EAAA,MAAM,EAAE,YAAA,EAAc,WAAa,EAAA,WAAA,KACjC,0BAA2B,EAAA;AAE7B,EAAA,MAAM,YAAY,YAAa,EAAA;AAC/B,EAAM,MAAA,MAAA,GAAS,SAAU,CAAA,GAAA,CAAI,YAAY,CAAA;AACzC,EAAM,MAAA,eAAA,GAAwC,QAAQ,MAAM;AAC1D,IAAO,OAAA,MAAA,EAAQ,IAAI,gBAAgB,CAAA,GAC9B,OAAO,GAAI,CAAA,gBAAgB,IAC5B,EAAC;AAAA,GACP,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,EAAE,SAAA,EAAW,QAAS,EAAA,GAAI,iBAAkB,EAAA;AAGlD,EAAM,MAAA,aAAA,GAAgB,QAAQ,MAAM;AAClC,IAAA,OAAO,CAAC,SAAa,IAAA,QAAA,GACjB,4BAA4B,eAAiB,EAAA,QAAQ,IACrD,EAAC;AAAA,GACJ,EAAA,CAAC,SAAW,EAAA,QAAA,EAAU,eAAe,CAAC,CAAA;AAGzC,EAAM,MAAA,aAAA,GAAgB,QAAQ,MAAM;AAClC,IAAO,OAAA,YAAA,GAAe,gBAAgB,EAAC;AAAA,GACtC,EAAA,CAAC,YAAc,EAAA,aAAa,CAAC,CAAA;AAKhC,EAAI,IAAA,eAAA,CAAgB,WAAW,CAAG,EAAA;AAChC,IAAO,OAAA,IAAA;AAAA;AAIT,EAAA,IAAI,CAAC,SAAA,IAAa,aAAc,CAAA,MAAA,KAAW,CAAG,EAAA;AAC5C,IAAO,OAAA,IAAA;AAAA;AAKT,EACE,uBAAA,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,EAAI,EAAA;AAAA,QACF,uBAAyB,EAAA;AAAA,UACvB,KAAO,EAAA,WAAA;AAAA,UACP,SAAW,EAAA,YAAA;AAAA,UACX,iBAAiB,CACf,KAAA,KAAA,CAAA,EACG,MAAsB,OAAS,EAAA,IAAA,EAAM,QACnC,sBACL,CAAA,CAAA;AAAA,UACF,cAAgB,EAAA;AAAA,SAClB;AAAA;AAAA,QAEA,4BAA8B,EAAA;AAAA,UAC5B,uBAAyB,EAAA;AAAA,YACvB,GAAK,EAAA,iBAAA;AAAA,YACL,MAAQ,EAAA;AAAA;AACV;AACF,OACF;AAAA,MACA,OAAQ,EAAA,YAAA;AAAA,MACR,MAAO,EAAA,OAAA;AAAA,MACP,IAAM,EAAA,YAAA;AAAA,MAEN,QAAA,kBAAA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,eAAiB,EAAA,aAAA;AAAA,UACjB,iBAAmB,EAAA,WAAA;AAAA,UACnB;AAAA;AAAA;AACF;AAAA,GACF;AAEJ;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useApi, identityApiRef, configApiRef } from '@backstage/core-plugin-api';
|
|
3
4
|
import Snackbar from '@mui/material/Snackbar';
|
|
4
5
|
import CloseIcon from '@mui/icons-material/Close';
|
|
5
6
|
import IconButton from '@mui/material/IconButton';
|
|
@@ -11,6 +12,9 @@ const QuickstartDrawerProvider = ({ children }) => {
|
|
|
11
12
|
const [showNotification, setShowNotification] = useState(false);
|
|
12
13
|
const [hasShownNotification, setHasShownNotification] = useState(false);
|
|
13
14
|
const [drawerWidth, setDrawerWidth] = useState(500);
|
|
15
|
+
const [userKey, setUserKey] = useState("guest");
|
|
16
|
+
const identityApi = useApi(identityApiRef);
|
|
17
|
+
const configApi = useApi(configApiRef);
|
|
14
18
|
useEffect(() => {
|
|
15
19
|
if (isDrawerOpen) {
|
|
16
20
|
document.body.classList.add("quickstart-drawer-open");
|
|
@@ -28,36 +32,74 @@ const QuickstartDrawerProvider = ({ children }) => {
|
|
|
28
32
|
};
|
|
29
33
|
}, [isDrawerOpen, drawerWidth]);
|
|
30
34
|
useEffect(() => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
(async () => {
|
|
37
|
+
try {
|
|
38
|
+
const identity = await identityApi.getBackstageIdentity();
|
|
39
|
+
const ref = identity?.userEntityRef?.toLowerCase() || "guest";
|
|
40
|
+
if (!cancelled) setUserKey(ref);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (!cancelled) setUserKey("guest");
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
};
|
|
48
|
+
}, [identityApi]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!userKey) return;
|
|
51
|
+
const hasAnyQuickstarts = (() => {
|
|
52
|
+
try {
|
|
53
|
+
if (!configApi?.has("app.quickstart")) return false;
|
|
54
|
+
const items = configApi.get("app.quickstart");
|
|
55
|
+
return Array.isArray(items) && items.length > 0;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
const openKey = `quickstart-open:${userKey}`;
|
|
61
|
+
const visitedKey = `quickstart-visited:${userKey}`;
|
|
62
|
+
const notificationKey = `quickstart-notification-shown:${userKey}`;
|
|
63
|
+
if (!hasAnyQuickstarts) {
|
|
64
|
+
setIsDrawerOpen(false);
|
|
65
|
+
localStorage.setItem(openKey, "false");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const wasOpen = localStorage.getItem(openKey);
|
|
69
|
+
const hasVisited = localStorage.getItem(visitedKey);
|
|
70
|
+
const notificationShown = localStorage.getItem(notificationKey);
|
|
36
71
|
if (!hasVisited) {
|
|
37
72
|
setIsDrawerOpen(true);
|
|
38
|
-
localStorage.setItem(
|
|
39
|
-
localStorage.setItem(
|
|
73
|
+
localStorage.setItem(visitedKey, "true");
|
|
74
|
+
localStorage.setItem(openKey, "true");
|
|
40
75
|
} else if (wasOpen === "true") {
|
|
41
76
|
setIsDrawerOpen(true);
|
|
77
|
+
} else {
|
|
78
|
+
setIsDrawerOpen(false);
|
|
42
79
|
}
|
|
43
80
|
setHasShownNotification(notificationShown === "true");
|
|
44
|
-
}, []);
|
|
81
|
+
}, [userKey, configApi]);
|
|
45
82
|
const openDrawer = () => {
|
|
46
83
|
setIsDrawerOpen(true);
|
|
47
|
-
|
|
84
|
+
const openKey = `quickstart-open:${userKey}`;
|
|
85
|
+
localStorage.setItem(openKey, "true");
|
|
48
86
|
};
|
|
49
87
|
const closeDrawer = () => {
|
|
50
88
|
setIsDrawerOpen(false);
|
|
51
89
|
if (!hasShownNotification) {
|
|
52
90
|
setShowNotification(true);
|
|
53
91
|
setHasShownNotification(true);
|
|
54
|
-
|
|
92
|
+
const notificationKey = `quickstart-notification-shown:${userKey}`;
|
|
93
|
+
localStorage.setItem(notificationKey, "true");
|
|
55
94
|
}
|
|
56
|
-
|
|
95
|
+
const openKey = `quickstart-open:${userKey}`;
|
|
96
|
+
localStorage.setItem(openKey, "false");
|
|
57
97
|
};
|
|
58
98
|
const toggleDrawer = () => {
|
|
59
|
-
|
|
60
|
-
|
|
99
|
+
const next = !isDrawerOpen;
|
|
100
|
+
setIsDrawerOpen(next);
|
|
101
|
+
const openKey = `quickstart-open:${userKey}`;
|
|
102
|
+
localStorage.setItem(openKey, next.toString());
|
|
61
103
|
};
|
|
62
104
|
const handleNotificationClose = () => setShowNotification(false);
|
|
63
105
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuickstartDrawerProvider.esm.js","sources":["../../src/components/QuickstartDrawerProvider.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, PropsWithChildren, useState } from 'react';\nimport Snackbar from '@mui/material/Snackbar';\nimport CloseIcon from '@mui/icons-material/Close';\nimport IconButton from '@mui/material/IconButton';\nimport { QuickstartDrawerContext } from './QuickstartDrawerContext';\nimport { QuickstartDrawer } from './QuickstartDrawer';\n\nexport const QuickstartDrawerProvider = ({ children }: PropsWithChildren) => {\n const [isDrawerOpen, setIsDrawerOpen] = useState<boolean>(false);\n const [showNotification, setShowNotification] = useState(false);\n const [hasShownNotification, setHasShownNotification] = useState(false);\n const [drawerWidth, setDrawerWidth] = useState<number>(500);\n\n // Single useEffect - sets class on document.body\n useEffect(() => {\n if (isDrawerOpen) {\n document.body.classList.add('quickstart-drawer-open');\n document.body.style.setProperty(\n '--quickstart-drawer-width',\n `${drawerWidth}px`,\n );\n } else {\n document.body.classList.remove('quickstart-drawer-open');\n document.body.style.removeProperty('--quickstart-drawer-width');\n }\n\n return () => {\n document.body.classList.remove('quickstart-drawer-open');\n document.body.style.removeProperty('--quickstart-drawer-width');\n };\n }, [isDrawerOpen, drawerWidth]);\n\n useEffect(() => {\n const wasOpen = localStorage.getItem('quickstart-open');\n const hasVisited = localStorage.getItem('quickstart-visited');\n const notificationShown = localStorage.getItem(\n 'quickstart-notification-shown',\n );\n\n if (!hasVisited) {\n setIsDrawerOpen(true);\n localStorage.setItem('quickstart-visited', 'true');\n localStorage.setItem('quickstart-open', 'true');\n } else if (wasOpen === 'true') {\n setIsDrawerOpen(true);\n }\n\n setHasShownNotification(notificationShown === 'true');\n }, []);\n\n const openDrawer = () => {\n setIsDrawerOpen(true);\n localStorage.setItem('quickstart-open', 'true');\n };\n\n const closeDrawer = () => {\n setIsDrawerOpen(false);\n if (!hasShownNotification) {\n setShowNotification(true);\n setHasShownNotification(true);\n localStorage.setItem('quickstart-notification-shown', 'true');\n }\n localStorage.setItem('quickstart-open', 'false');\n };\n\n const toggleDrawer = () => {\n setIsDrawerOpen(!isDrawerOpen);\n localStorage.setItem('quickstart-open', (!isDrawerOpen).toString());\n };\n\n const handleNotificationClose = () => setShowNotification(false);\n\n return (\n <QuickstartDrawerContext.Provider\n value={{\n isDrawerOpen,\n openDrawer,\n closeDrawer,\n toggleDrawer,\n setDrawerWidth,\n drawerWidth,\n }}\n >\n {children}\n <QuickstartDrawer />\n <Snackbar\n sx={{ top: '80px !important' }}\n open={showNotification}\n autoHideDuration={10000}\n onClose={handleNotificationClose}\n anchorOrigin={{ vertical: 'top', horizontal: 'right' }}\n message=\"Need help? Visit the Quick Start Guide by clicking on this (?) icon in the header!\"\n action={\n <IconButton\n size=\"small\"\n aria-label=\"close\"\n color=\"inherit\"\n onClick={handleNotificationClose}\n >\n <CloseIcon fontSize=\"small\" />\n </IconButton>\n }\n />\n </QuickstartDrawerContext.Provider>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"QuickstartDrawerProvider.esm.js","sources":["../../src/components/QuickstartDrawerProvider.tsx"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useEffect, PropsWithChildren, useState } from 'react';\nimport {\n configApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\nimport Snackbar from '@mui/material/Snackbar';\nimport CloseIcon from '@mui/icons-material/Close';\nimport IconButton from '@mui/material/IconButton';\nimport { QuickstartDrawerContext } from './QuickstartDrawerContext';\nimport { QuickstartDrawer } from './QuickstartDrawer';\n\nexport const QuickstartDrawerProvider = ({ children }: PropsWithChildren) => {\n const [isDrawerOpen, setIsDrawerOpen] = useState<boolean>(false);\n const [showNotification, setShowNotification] = useState(false);\n const [hasShownNotification, setHasShownNotification] = useState(false);\n const [drawerWidth, setDrawerWidth] = useState<number>(500);\n const [userKey, setUserKey] = useState<string>('guest');\n const identityApi = useApi(identityApiRef);\n const configApi = useApi(configApiRef);\n\n // Single useEffect - sets class on document.body\n useEffect(() => {\n if (isDrawerOpen) {\n document.body.classList.add('quickstart-drawer-open');\n document.body.style.setProperty(\n '--quickstart-drawer-width',\n `${drawerWidth}px`,\n );\n } else {\n document.body.classList.remove('quickstart-drawer-open');\n document.body.style.removeProperty('--quickstart-drawer-width');\n }\n\n return () => {\n document.body.classList.remove('quickstart-drawer-open');\n document.body.style.removeProperty('--quickstart-drawer-width');\n };\n }, [isDrawerOpen, drawerWidth]);\n\n // Resolve the current user's identity to scope localStorage keys per user\n useEffect(() => {\n let cancelled = false;\n (async () => {\n try {\n const identity = await identityApi.getBackstageIdentity();\n const ref = identity?.userEntityRef?.toLowerCase() || 'guest';\n if (!cancelled) setUserKey(ref);\n } catch (e) {\n if (!cancelled) setUserKey('guest');\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [identityApi]);\n\n // Initialize drawer state based on per-user keys and only when quickstarts exist\n useEffect(() => {\n if (!userKey) return;\n\n // Determine if there are any quickstart items configured globally\n const hasAnyQuickstarts = (() => {\n try {\n if (!configApi?.has('app.quickstart')) return false;\n const items = configApi.get('app.quickstart') as unknown;\n return Array.isArray(items) && items.length > 0;\n } catch {\n return false;\n }\n })();\n\n const openKey = `quickstart-open:${userKey}`;\n const visitedKey = `quickstart-visited:${userKey}`;\n const notificationKey = `quickstart-notification-shown:${userKey}`;\n\n // If no quickstarts are configured, ensure the drawer is closed and don't mark as visited\n if (!hasAnyQuickstarts) {\n setIsDrawerOpen(false);\n // Avoid persisting visited so future addition of items can auto-open\n localStorage.setItem(openKey, 'false');\n return;\n }\n\n const wasOpen = localStorage.getItem(openKey);\n const hasVisited = localStorage.getItem(visitedKey);\n const notificationShown = localStorage.getItem(notificationKey);\n\n if (!hasVisited) {\n setIsDrawerOpen(true);\n localStorage.setItem(visitedKey, 'true');\n localStorage.setItem(openKey, 'true');\n } else if (wasOpen === 'true') {\n setIsDrawerOpen(true);\n } else {\n setIsDrawerOpen(false);\n }\n\n setHasShownNotification(notificationShown === 'true');\n }, [userKey, configApi]);\n\n const openDrawer = () => {\n setIsDrawerOpen(true);\n const openKey = `quickstart-open:${userKey}`;\n localStorage.setItem(openKey, 'true');\n };\n\n const closeDrawer = () => {\n setIsDrawerOpen(false);\n if (!hasShownNotification) {\n setShowNotification(true);\n setHasShownNotification(true);\n const notificationKey = `quickstart-notification-shown:${userKey}`;\n localStorage.setItem(notificationKey, 'true');\n }\n const openKey = `quickstart-open:${userKey}`;\n localStorage.setItem(openKey, 'false');\n };\n\n const toggleDrawer = () => {\n const next = !isDrawerOpen;\n setIsDrawerOpen(next);\n const openKey = `quickstart-open:${userKey}`;\n localStorage.setItem(openKey, next.toString());\n };\n\n const handleNotificationClose = () => setShowNotification(false);\n\n return (\n <QuickstartDrawerContext.Provider\n value={{\n isDrawerOpen,\n openDrawer,\n closeDrawer,\n toggleDrawer,\n setDrawerWidth,\n drawerWidth,\n }}\n >\n {children}\n <QuickstartDrawer />\n <Snackbar\n sx={{ top: '80px !important' }}\n open={showNotification}\n autoHideDuration={10000}\n onClose={handleNotificationClose}\n anchorOrigin={{ vertical: 'top', horizontal: 'right' }}\n message=\"Need help? Visit the Quick Start Guide by clicking on this (?) icon in the header!\"\n action={\n <IconButton\n size=\"small\"\n aria-label=\"close\"\n color=\"inherit\"\n onClick={handleNotificationClose}\n >\n <CloseIcon fontSize=\"small\" />\n </IconButton>\n }\n />\n </QuickstartDrawerContext.Provider>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -15,22 +15,39 @@ const useQuickstartRole = () => {
|
|
|
15
15
|
const credentials = await identityApi.getCredentials();
|
|
16
16
|
const identity = await identityApi.getBackstageIdentity();
|
|
17
17
|
const hasValidToken = credentials?.token && credentials.token.length > 10;
|
|
18
|
-
const
|
|
18
|
+
const userEntityRef2 = identity?.userEntityRef || "";
|
|
19
19
|
const ownershipRefs = identity?.ownershipEntityRefs || [];
|
|
20
|
-
const isGuest =
|
|
20
|
+
const isGuest = userEntityRef2.toLowerCase().includes("guest") || userEntityRef2 === "user:default/guest" || !hasValidToken && ownershipRefs.length === 0;
|
|
21
21
|
const isAuthenticated = !isGuest;
|
|
22
22
|
return { isAuthenticated, identity, credentials };
|
|
23
23
|
} catch (error) {
|
|
24
24
|
return { isAuthenticated: false, identity: null, credentials: null };
|
|
25
25
|
}
|
|
26
26
|
}, [identityApi]);
|
|
27
|
-
if (authLoading
|
|
27
|
+
if (authLoading) return { isLoading: true, userRole: null };
|
|
28
|
+
const userEntityRef = authResult?.identity?.userEntityRef || "guest";
|
|
29
|
+
const cacheKey = `quickstart-role:${userEntityRef}:rbac:${isRBACEnabled ? "1" : "0"}`;
|
|
30
|
+
const cachedRole = typeof window !== "undefined" ? sessionStorage.getItem(cacheKey) : null;
|
|
31
|
+
if (cachedRole === "admin" || cachedRole === "developer") {
|
|
32
|
+
return { isLoading: false, userRole: cachedRole };
|
|
33
|
+
}
|
|
34
|
+
if (loading) return { isLoading: true, userRole: null };
|
|
28
35
|
const isUserAuthorized = authResult?.isAuthenticated ?? false;
|
|
29
36
|
if (!isUserAuthorized) {
|
|
30
37
|
return { isLoading: false, userRole: "admin" };
|
|
31
38
|
}
|
|
32
|
-
if (!isRBACEnabled)
|
|
33
|
-
|
|
39
|
+
if (!isRBACEnabled) {
|
|
40
|
+
if (typeof window !== "undefined")
|
|
41
|
+
sessionStorage.setItem(cacheKey, "admin");
|
|
42
|
+
return { isLoading: false, userRole: "admin" };
|
|
43
|
+
}
|
|
44
|
+
if (allowed) {
|
|
45
|
+
if (typeof window !== "undefined")
|
|
46
|
+
sessionStorage.setItem(cacheKey, "admin");
|
|
47
|
+
return { isLoading: false, userRole: "admin" };
|
|
48
|
+
}
|
|
49
|
+
if (typeof window !== "undefined")
|
|
50
|
+
sessionStorage.setItem(cacheKey, "developer");
|
|
34
51
|
return { isLoading: false, userRole: "developer" };
|
|
35
52
|
};
|
|
36
53
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useQuickstartRole.esm.js","sources":["../../src/hooks/useQuickstartRole.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { usePermission } from '@backstage/plugin-permission-react';\nimport {\n configApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\nimport { policyEntityCreatePermission } from '@backstage-community/plugin-rbac-common';\nimport { useAsync } from 'react-use';\nimport { UserRole } from '../types';\n\n/**\n * Determines the user's role for quickstart functionality based on RBAC permissions and user authorization.\n *\n * Business Logic:\n * - Guest user(unauthorized): show admin items\n * - Authorized user + NO RBAC enabled: show admin items\n * - Authorized user + RBAC enabled:\n * - if user has admin permission => show configured admin items\n * - if user doesn't have admin permission => show configured developer items\n *\n * @returns Object with isLoading boolean and userRole ('admin' | 'developer' | null)\n */\nexport const useQuickstartRole = (): {\n isLoading: boolean;\n userRole: UserRole | null;\n} => {\n const config = useApi(configApiRef);\n const identityApi = useApi(identityApiRef);\n const isRBACEnabled =\n config.getOptionalBoolean('permission.enabled') ?? false;\n const { loading, allowed } = usePermission({\n permission: policyEntityCreatePermission,\n });\n\n // Check user authorization status by examining identity and credentials\n const { value: authResult, loading: authLoading } = useAsync(async () => {\n try {\n const credentials = await identityApi.getCredentials();\n const identity = await identityApi.getBackstageIdentity();\n\n // Check multiple indicators to determine if user is authenticated (not a guest)\n const hasValidToken = credentials?.token && credentials.token.length > 10; // Real tokens are longer\n const userEntityRef = identity?.userEntityRef || '';\n const ownershipRefs = identity?.ownershipEntityRefs || [];\n\n const isGuest =\n userEntityRef.toLowerCase().includes('guest') ||\n userEntityRef === 'user:default/guest' ||\n (!hasValidToken && ownershipRefs.length === 0);\n\n const isAuthenticated = !isGuest;\n\n return { isAuthenticated, identity, credentials };\n } catch (error) {\n return { isAuthenticated: false, identity: null, credentials: null };\n }\n }, [identityApi]);\n\n //
|
|
1
|
+
{"version":3,"file":"useQuickstartRole.esm.js","sources":["../../src/hooks/useQuickstartRole.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { usePermission } from '@backstage/plugin-permission-react';\nimport {\n configApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\nimport { policyEntityCreatePermission } from '@backstage-community/plugin-rbac-common';\nimport { useAsync } from 'react-use';\nimport { UserRole } from '../types';\n\n/**\n * Determines the user's role for quickstart functionality based on RBAC permissions and user authorization.\n *\n * Business Logic:\n * - Guest user(unauthorized): show admin items\n * - Authorized user + NO RBAC enabled: show admin items\n * - Authorized user + RBAC enabled:\n * - if user has admin permission => show configured admin items\n * - if user doesn't have admin permission => show configured developer items\n *\n * @returns Object with isLoading boolean and userRole ('admin' | 'developer' | null)\n */\nexport const useQuickstartRole = (): {\n isLoading: boolean;\n userRole: UserRole | null;\n} => {\n const config = useApi(configApiRef);\n const identityApi = useApi(identityApiRef);\n const isRBACEnabled =\n config.getOptionalBoolean('permission.enabled') ?? false;\n const { loading, allowed } = usePermission({\n permission: policyEntityCreatePermission,\n });\n\n // Check user authorization status by examining identity and credentials\n const { value: authResult, loading: authLoading } = useAsync(async () => {\n try {\n const credentials = await identityApi.getCredentials();\n const identity = await identityApi.getBackstageIdentity();\n\n // Check multiple indicators to determine if user is authenticated (not a guest)\n const hasValidToken = credentials?.token && credentials.token.length > 10; // Real tokens are longer\n const userEntityRef = identity?.userEntityRef || '';\n const ownershipRefs = identity?.ownershipEntityRefs || [];\n\n const isGuest =\n userEntityRef.toLowerCase().includes('guest') ||\n userEntityRef === 'user:default/guest' ||\n (!hasValidToken && ownershipRefs.length === 0);\n\n const isAuthenticated = !isGuest;\n\n return { isAuthenticated, identity, credentials };\n } catch (error) {\n return { isAuthenticated: false, identity: null, credentials: null };\n }\n }, [identityApi]);\n\n // When auth is still resolving, return loading\n if (authLoading) return { isLoading: true, userRole: null };\n\n // After auth resolves, attempt to serve cached role (session scoped)\n const userEntityRef = authResult?.identity?.userEntityRef || 'guest';\n const cacheKey = `quickstart-role:${userEntityRef}:rbac:${\n isRBACEnabled ? '1' : '0'\n }`;\n const cachedRole =\n typeof window !== 'undefined' ? sessionStorage.getItem(cacheKey) : null;\n if (cachedRole === 'admin' || cachedRole === 'developer') {\n return { isLoading: false, userRole: cachedRole as UserRole };\n }\n\n // If permission is still loading and there's no cache, report loading\n if (loading) return { isLoading: true, userRole: null };\n\n // Check if user is authorized (authenticated, not a guest)\n const isUserAuthorized = authResult?.isAuthenticated ?? false;\n\n // Unauthorized user: show admin items\n if (!isUserAuthorized) {\n return { isLoading: false, userRole: 'admin' };\n }\n\n // Authorized user + NO RBAC enabled: show admin items\n if (!isRBACEnabled) {\n if (typeof window !== 'undefined')\n sessionStorage.setItem(cacheKey, 'admin');\n return { isLoading: false, userRole: 'admin' };\n }\n\n // Authorized user + RBAC enabled: check permissions\n // If user has admin permission => show configured admin items\n if (allowed) {\n if (typeof window !== 'undefined')\n sessionStorage.setItem(cacheKey, 'admin');\n return { isLoading: false, userRole: 'admin' };\n }\n\n // If user doesn't have admin permission => show configured developer items\n if (typeof window !== 'undefined')\n sessionStorage.setItem(cacheKey, 'developer');\n return { isLoading: false, userRole: 'developer' };\n};\n"],"names":["userEntityRef"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@red-hat-developer-hub/backstage-plugin-quickstart",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "dist/index.esm.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -33,24 +33,24 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@backstage-community/plugin-rbac-common": "^1.19.0",
|
|
36
|
-
"@backstage/core-components": "^0.17.
|
|
36
|
+
"@backstage/core-components": "^0.17.5",
|
|
37
37
|
"@backstage/core-plugin-api": "^1.10.9",
|
|
38
38
|
"@backstage/plugin-permission-react": "^0.4.36",
|
|
39
|
-
"@backstage/theme": "^0.6.
|
|
39
|
+
"@backstage/theme": "^0.6.8",
|
|
40
40
|
"@mui/icons-material": "5.18.0",
|
|
41
41
|
"@mui/material": "5.18.0",
|
|
42
|
-
"@red-hat-developer-hub/backstage-plugin-theme": "^0.9.0",
|
|
43
42
|
"react-use": "^17.6.0"
|
|
44
43
|
},
|
|
45
44
|
"peerDependencies": {
|
|
46
45
|
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
|
47
46
|
},
|
|
48
47
|
"devDependencies": {
|
|
49
|
-
"@backstage/cli": "^0.
|
|
48
|
+
"@backstage/cli": "^0.34.1",
|
|
50
49
|
"@backstage/core-app-api": "^1.18.0",
|
|
51
|
-
"@backstage/dev-utils": "^1.1.
|
|
50
|
+
"@backstage/dev-utils": "^1.1.13",
|
|
52
51
|
"@backstage/plugin-permission-common": "^0.9.1",
|
|
53
|
-
"@backstage/test-utils": "^1.7.
|
|
52
|
+
"@backstage/test-utils": "^1.7.11",
|
|
53
|
+
"@red-hat-developer-hub/backstage-plugin-theme": "^0.10.0",
|
|
54
54
|
"@testing-library/jest-dom": "^6.0.0",
|
|
55
55
|
"@testing-library/react": "^14.0.0",
|
|
56
56
|
"@testing-library/user-event": "^14.0.0",
|