11import React from 'react' ;
22import { useHistory } from 'react-router-dom' ;
33import { Button , EmptyStateVariant } from '@patternfly/react-core' ;
4- import { SearchIcon } from '@patternfly/react-icons' ;
4+ import { LockIcon , SearchIcon } from '@patternfly/react-icons' ;
55import EmptyStatePattern from './EmptyStatePattern' ;
66import { resourceLoadFailedEmptyStatePropTypes } from './EmptyStatePropTypes' ;
77import { translate as __ , sprintf } from '../../../common/I18n' ;
8+ import { usePermissions } from '../../../common/hooks/Permissions/permissionHooks' ;
9+ import { useForemanPermissions } from '../../../Root/Context/ForemanContext' ;
10+
11+ const FAILURE_REASON = {
12+ FORBIDDEN : 'forbidden' ,
13+ NOT_FOUND : 'not_found' ,
14+ UNKNOWN : 'unknown' ,
15+ } ;
16+
17+ const resolveFailureReason = ( {
18+ httpStatus,
19+ viewPermissions,
20+ hasViewPermission,
21+ } ) => {
22+ if ( httpStatus === 403 ) return FAILURE_REASON . FORBIDDEN ;
23+ if ( viewPermissions ?. length > 0 && ! hasViewPermission ) {
24+ return FAILURE_REASON . FORBIDDEN ;
25+ }
26+ if ( httpStatus === 404 ) return FAILURE_REASON . NOT_FOUND ;
27+ if ( viewPermissions ?. length > 0 && hasViewPermission ) {
28+ return FAILURE_REASON . NOT_FOUND ;
29+ }
30+ return FAILURE_REASON . UNKNOWN ;
31+ } ;
32+
33+ const getDefaultDescription = ( failureReason , resourceLabel , resourceId ) => {
34+ const hasResourceId = resourceId !== null && resourceId !== undefined ;
35+
36+ switch ( failureReason ) {
37+ case FAILURE_REASON . FORBIDDEN :
38+ return hasResourceId
39+ ? sprintf (
40+ __ ( 'You do not have permission to view the %s with id %s.' ) ,
41+ resourceLabel ,
42+ resourceId
43+ )
44+ : sprintf (
45+ __ ( 'You do not have permission to view this %s.' ) ,
46+ resourceLabel
47+ ) ;
48+ case FAILURE_REASON . NOT_FOUND :
49+ return hasResourceId
50+ ? sprintf (
51+ __ (
52+ 'The %s with id %s could not be found. It may have been deleted or may not be available in your current organization or location scope.'
53+ ) ,
54+ resourceLabel ,
55+ resourceId
56+ )
57+ : sprintf (
58+ __ (
59+ 'The %s could not be found. It may have been deleted or may not be available in your current organization or location scope.'
60+ ) ,
61+ resourceLabel
62+ ) ;
63+ default :
64+ return hasResourceId
65+ ? sprintf (
66+ __ ( 'The %s with id %s could not be loaded.' ) ,
67+ resourceLabel ,
68+ resourceId
69+ )
70+ : sprintf ( __ ( 'The %s could not be loaded.' ) , resourceLabel ) ;
71+ }
72+ } ;
873
974const invokeFooterAction = ( action , history ) => {
1075 if ( action . onClick ) {
@@ -20,6 +85,8 @@ const ResourceLoadFailedEmptyState = ({
2085 header,
2186 description,
2287 errorMessage,
88+ httpStatus,
89+ viewPermissions,
2390 requiredPermissions,
2491 primaryAction,
2592 secondaryActions,
@@ -31,35 +98,47 @@ const ResourceLoadFailedEmptyState = ({
3198 ...props
3299} ) => {
33100 const history = useHistory ( ) ;
101+ const userPermissions = useForemanPermissions ( ) ;
102+ const hasViewPermission = usePermissions ( viewPermissions || [ ] ) ;
103+ const failureReason = resolveFailureReason ( {
104+ httpStatus,
105+ viewPermissions,
106+ hasViewPermission,
107+ } ) ;
108+ const isForbidden = failureReason === FAILURE_REASON . FORBIDDEN ;
109+
110+ const missingViewPermissions =
111+ viewPermissions ?. filter ( permission => ! userPermissions . has ( permission ) ) ||
112+ [ ] ;
113+
114+ let permissionsToList = requiredPermissions ;
115+ if ( isForbidden && missingViewPermissions . length > 0 ) {
116+ permissionsToList = missingViewPermissions ;
117+ }
34118
35119 const resolvedHeader =
36- header || sprintf ( __ ( 'Unable to load %s' ) , resourceLabel ) ;
37-
38- const defaultDescription =
39- resourceId !== null && resourceId !== undefined
40- ? sprintf (
41- __ (
42- 'The %s with id %s could not be loaded. It may not exist or you may not have permission to view it.'
43- ) ,
44- resourceLabel ,
45- resourceId
46- )
47- : sprintf (
48- __ (
49- 'The %s could not be loaded. It may not exist or you may not have permission to view it.'
50- ) ,
51- resourceLabel
52- ) ;
120+ header ||
121+ ( isForbidden
122+ ? __ ( 'Permission denied' )
123+ : sprintf ( __ ( 'Unable to load %s' ) , resourceLabel ) ) ;
124+
125+ const resolvedIcon = icon ?? ( isForbidden ? < LockIcon /> : < SearchIcon /> ) ;
126+
127+ const defaultDescription = getDefaultDescription (
128+ failureReason ,
129+ resourceLabel ,
130+ resourceId
131+ ) ;
53132
54133 const descriptionContent = (
55134 < React . Fragment >
56135 < p >
57136 { description ?? defaultDescription }
58- { requiredPermissions ?. length > 0 ? (
137+ { permissionsToList ?. length > 0 ? (
59138 < React . Fragment >
60139 { ' ' }
61140 { __ ( 'Accessing this page requires the following permissions:' ) } { ' ' }
62- { requiredPermissions . map ( ( permission , index ) => (
141+ { permissionsToList . map ( ( permission , index ) => (
63142 < React . Fragment key = { permission } >
64143 { index > 0 ? ', ' : null }
65144 < strong > { permission } </ strong >
@@ -120,7 +199,7 @@ const ResourceLoadFailedEmptyState = ({
120199 return (
121200 < EmptyStatePattern
122201 variant = { variant }
123- icon = { icon }
202+ icon = { resolvedIcon }
124203 header = { resolvedHeader }
125204 description = { descriptionContent }
126205 action = { renderFooterButton (
@@ -141,11 +220,13 @@ ResourceLoadFailedEmptyState.defaultProps = {
141220 header : null ,
142221 description : null ,
143222 errorMessage : null ,
223+ httpStatus : null ,
224+ viewPermissions : null ,
144225 requiredPermissions : null ,
145226 secondaryActions : [ ] ,
146227 showBackButton : true ,
147228 backButtonLabel : __ ( 'Return to the previous page' ) ,
148- icon : < SearchIcon /> ,
229+ icon : null ,
149230 variant : EmptyStateVariant . lg ,
150231 ouiaIdPrefix : 'resource-load-failed-empty-state' ,
151232} ;
0 commit comments