| 1 |
/** |
| 2 |
* WordPress dependencies |
| 3 |
*/ |
| 4 |
import { |
| 5 |
Button, |
| 6 |
__experimentalInputControlPrefixWrapper as InputControlPrefixWrapper, |
| 7 |
MenuGroup, |
| 8 |
MenuItem, |
| 9 |
SelectControl, |
| 10 |
} from '@wordpress/components'; |
| 11 |
import { useEffect, useState } from '@wordpress/element'; |
| 12 |
import { __ } from '@wordpress/i18n'; |
| 13 |
import { |
| 14 |
check, |
| 15 |
moreVertical, |
| 16 |
reset, |
| 17 |
} from '@wordpress/icons'; |
| 18 |
import { Telemetry } from '../../../js/telemetry/telemetry'; |
| 19 |
|
| 20 |
/** |
| 21 |
* Internal dependencies |
| 22 |
*/ |
| 23 |
import { ContentHelperError } from '../../common/content-helper-error'; |
| 24 |
import { SidebarSettings, useSettings } from '../../common/settings'; |
| 25 |
import { |
| 26 |
Period, |
| 27 |
getPeriodDescription, |
| 28 |
isInEnum, |
| 29 |
} from '../../common/utils/constants'; |
| 30 |
import { PerformanceStatPanel } from './component-panel'; |
| 31 |
import { PerformanceCategoriesPanel } from './component-panel-categories'; |
| 32 |
import { PerformanceOverviewPanel } from './component-panel-overview'; |
| 33 |
import { PerformanceReferrersPanel } from './component-panel-referrers'; |
| 34 |
import { PerformanceData } from './model'; |
| 35 |
import './performance-stats.scss'; |
| 36 |
import { PerformanceStatsProvider } from './provider'; |
| 37 |
|
| 38 |
/** |
| 39 |
* The maximum number of retries for fetching the performance stats. |
| 40 |
* |
| 41 |
* @since 3.20.0 Renamed from FETCH_RETRIES to MAX_FETCH_RETRIES. |
| 42 |
*/ |
| 43 |
const MAX_FETCH_RETRIES = 1; |
| 44 |
|
| 45 |
/** |
| 46 |
* Panel metadata descriptor. |
| 47 |
* |
| 48 |
* @since 3.14.0 |
| 49 |
*/ |
| 50 |
type PanelDescriptor = { |
| 51 |
name: string; |
| 52 |
label: string; |
| 53 |
forced?: boolean; |
| 54 |
}; |
| 55 |
|
| 56 |
/** |
| 57 |
* List of available panels to display in the Performance Stats menu. |
| 58 |
* |
| 59 |
* @since 3.14.0 |
| 60 |
*/ |
| 61 |
const availablePanels: PanelDescriptor[] = [ |
| 62 |
{ |
| 63 |
name: 'overview', |
| 64 |
label: __( 'Overview', 'wp-parsely' ), |
| 65 |
}, |
| 66 |
{ |
| 67 |
name: 'categories', |
| 68 |
label: __( 'Referrer Categories', 'wp-parsely' ), |
| 69 |
}, |
| 70 |
{ |
| 71 |
name: 'referrers', |
| 72 |
label: __( 'Referrers', 'wp-parsely' ), |
| 73 |
}, |
| 74 |
]; |
| 75 |
|
| 76 |
/** |
| 77 |
* Checks if a panel is visible in the sidebar settings. |
| 78 |
* |
| 79 |
* @since 3.14.0 |
| 80 |
* |
| 81 |
* @param {SidebarSettings} settings The sidebar settings. |
| 82 |
* @param {string} panel The name of the panel. |
| 83 |
* |
| 84 |
* @return {boolean} True if the panel is visible, false otherwise. |
| 85 |
*/ |
| 86 |
const isPanelVisible = ( settings: SidebarSettings, panel: string ): boolean => { |
| 87 |
return settings.PerformanceStats.VisiblePanels.includes( panel ); |
| 88 |
}; |
| 89 |
|
| 90 |
/** |
| 91 |
* PerformanceStatsMenu dropdown menu component. |
| 92 |
* |
| 93 |
* @since 3.14.0 |
| 94 |
* |
| 95 |
* @param {Function} onClose Callback to close the dropdown menu. |
| 96 |
* |
| 97 |
* @return {import('react').JSX.Element} The dropdown menu JSX Element. |
| 98 |
*/ |
| 99 |
const PerformanceStatsMenu = ( |
| 100 |
{ onClose }: { onClose: () => void } |
| 101 |
): React.JSX.Element => { |
| 102 |
const { settings, setSettings } = useSettings<SidebarSettings>(); |
| 103 |
|
| 104 |
/** |
| 105 |
* Toggles a panel's visibility in the sidebar settings. |
| 106 |
* If the panel is forced, it will not be toggled. |
| 107 |
* |
| 108 |
* @since 3.14.0 |
| 109 |
* |
| 110 |
* @param {string} panel The name of the panel to toggle. |
| 111 |
*/ |
| 112 |
const togglePanel = ( panel: string ): void => { |
| 113 |
// Do not toggle panels that are forced to be visible. |
| 114 |
if ( availablePanels.find( ( p ) => p.name === panel )?.forced ) { |
| 115 |
return; |
| 116 |
} |
| 117 |
|
| 118 |
// Check if the panel is in the settings.PerformanceStats.VisiblePanels array |
| 119 |
// If it is, remove it with setSettings, if not, add it. |
| 120 |
if ( isPanelVisible( settings, panel ) ) { |
| 121 |
setSettings( { |
| 122 |
PerformanceStats: { |
| 123 |
...settings.PerformanceStats, |
| 124 |
VisiblePanels: settings.PerformanceStats.VisiblePanels.filter( |
| 125 |
( p ) => p !== panel |
| 126 |
), |
| 127 |
}, |
| 128 |
} ); |
| 129 |
Telemetry.trackEvent( 'editor_sidebar_performance_panel_closed', { panel } ); |
| 130 |
} else { |
| 131 |
setSettings( { |
| 132 |
PerformanceStats: { |
| 133 |
...settings.PerformanceStats, |
| 134 |
VisiblePanels: [ ...settings.PerformanceStats.VisiblePanels, panel ], |
| 135 |
}, |
| 136 |
} ); |
| 137 |
Telemetry.trackEvent( 'editor_sidebar_performance_panel_opened', { panel } ); |
| 138 |
} |
| 139 |
}; |
| 140 |
|
| 141 |
/** |
| 142 |
* Handles the click event on a menu item. |
| 143 |
* |
| 144 |
* @since 3.14.0 |
| 145 |
* |
| 146 |
* @param {string} selection The name of the selected panel. |
| 147 |
*/ |
| 148 |
const onClick = ( selection: string ): void => { |
| 149 |
togglePanel( selection ); |
| 150 |
onClose(); |
| 151 |
}; |
| 152 |
|
| 153 |
/** |
| 154 |
* Resets all panels to their default visibility. |
| 155 |
* |
| 156 |
* @since 3.14.0 |
| 157 |
*/ |
| 158 |
const resetAll = (): void => { |
| 159 |
setSettings( { |
| 160 |
PerformanceStats: { |
| 161 |
...settings.PerformanceStats, |
| 162 |
VisiblePanels: availablePanels.map( ( panel ) => panel.name ), |
| 163 |
}, |
| 164 |
} ); |
| 165 |
Telemetry.trackEvent( 'editor_sidebar_performance_panels_reset' ); |
| 166 |
onClose(); |
| 167 |
}; |
| 168 |
|
| 169 |
return ( |
| 170 |
<> |
| 171 |
<MenuGroup label={ __( 'Performance Stats', 'wp-parsely' ) }> |
| 172 |
{ availablePanels.map( ( item ) => ( |
| 173 |
<MenuItem |
| 174 |
key={ item.name } |
| 175 |
disabled={ item.forced } |
| 176 |
icon={ isPanelVisible( settings, item.name ) ? check : reset } |
| 177 |
onClick={ () => onClick( item.name ) } |
| 178 |
> |
| 179 |
{ item.label } |
| 180 |
</MenuItem> |
| 181 |
) ) } |
| 182 |
</MenuGroup> |
| 183 |
<MenuGroup> |
| 184 |
<MenuItem onClick={ resetAll }> |
| 185 |
{ __( 'Reset all', 'wp-parsely' ) } |
| 186 |
</MenuItem> |
| 187 |
</MenuGroup> |
| 188 |
</> |
| 189 |
); |
| 190 |
}; |
| 191 |
|
| 192 |
/** |
| 193 |
* PerformanceStats component properties. |
| 194 |
* |
| 195 |
* @since 3.14.0 |
| 196 |
*/ |
| 197 |
type PerformanceStatsProps = { |
| 198 |
period: Period; |
| 199 |
} |
| 200 |
|
| 201 |
/** |
| 202 |
* PerformanceStats component. |
| 203 |
* |
| 204 |
* @since 3.14.0 |
| 205 |
* |
| 206 |
* @param {PerformanceStatsProps} props The component's properties. |
| 207 |
*/ |
| 208 |
export const PerformanceStats = ( |
| 209 |
{ period }: PerformanceStatsProps |
| 210 |
): React.JSX.Element => { |
| 211 |
const [ loading, setLoading ] = useState<boolean>( true ); |
| 212 |
const [ error, setError ] = useState<ContentHelperError>(); |
| 213 |
const [ postDetails, setPostDetails ] = useState<PerformanceData>(); |
| 214 |
|
| 215 |
const { settings, setSettings } = useSettings<SidebarSettings>(); |
| 216 |
|
| 217 |
useEffect( () => { |
| 218 |
const provider = PerformanceStatsProvider.getInstance(); |
| 219 |
|
| 220 |
const fetchPosts = async ( retries: number ) => { |
| 221 |
provider.getPerformanceStats( period ) |
| 222 |
.then( ( result ) => { |
| 223 |
setPostDetails( result ); |
| 224 |
setLoading( false ); |
| 225 |
} ) |
| 226 |
.catch( async ( err ) => { |
| 227 |
if ( retries > 0 && err.retryFetch ) { |
| 228 |
await new Promise( ( r ) => setTimeout( r, 500 ) ); |
| 229 |
await fetchPosts( retries - 1 ); |
| 230 |
} else { |
| 231 |
setError( err ); |
| 232 |
setLoading( false ); |
| 233 |
} |
| 234 |
} ); |
| 235 |
}; |
| 236 |
|
| 237 |
setLoading( true ); |
| 238 |
fetchPosts( MAX_FETCH_RETRIES ); |
| 239 |
|
| 240 |
return (): void => { |
| 241 |
setError( undefined ); |
| 242 |
}; |
| 243 |
}, [ period ] ); |
| 244 |
|
| 245 |
return ( |
| 246 |
<div className="wp-parsely-performance-panel"> |
| 247 |
<PerformanceStatPanel |
| 248 |
title={ __( 'Performance Stats', 'wp-parsely' ) } |
| 249 |
icon={ moreVertical } |
| 250 |
dropdownChildren={ ( { onClose } ) => <PerformanceStatsMenu onClose={ onClose } /> } |
| 251 |
> |
| 252 |
<div className="panel-settings"> |
| 253 |
<SelectControl |
| 254 |
size="__unstable-large" |
| 255 |
value={ settings.PerformanceStats.Period } |
| 256 |
prefix={ |
| 257 |
<InputControlPrefixWrapper> |
| 258 |
{ __( 'Period:', 'wp-parsely' ) } |
| 259 |
</InputControlPrefixWrapper> |
| 260 |
} |
| 261 |
onChange={ ( selection ) => { |
| 262 |
if ( isInEnum( selection, Period ) ) { |
| 263 |
setSettings( { |
| 264 |
PerformanceStats: { |
| 265 |
...settings.PerformanceStats, |
| 266 |
Period: selection as Period, |
| 267 |
}, |
| 268 |
} ); |
| 269 |
Telemetry.trackEvent( |
| 270 |
'editor_sidebar_performance_period_changed', |
| 271 |
{ period: selection } |
| 272 |
); |
| 273 |
} |
| 274 |
} } |
| 275 |
> |
| 276 |
{ Object.values( Period ).map( ( value ) => ( |
| 277 |
<option key={ value } value={ value }> |
| 278 |
{ getPeriodDescription( value ) } |
| 279 |
</option> |
| 280 |
) ) } |
| 281 |
</SelectControl> |
| 282 |
</div> |
| 283 |
</PerformanceStatPanel> |
| 284 |
|
| 285 |
{ error ? ( |
| 286 |
error.Message() |
| 287 |
) : ( |
| 288 |
<> |
| 289 |
{ isPanelVisible( settings, 'overview' ) && ( |
| 290 |
<PerformanceOverviewPanel |
| 291 |
data={ postDetails as PerformanceData } |
| 292 |
isLoading={ loading } |
| 293 |
/> |
| 294 |
) } |
| 295 |
{ isPanelVisible( settings, 'categories' ) && ( |
| 296 |
<PerformanceCategoriesPanel |
| 297 |
data={ postDetails as PerformanceData } |
| 298 |
isLoading={ loading } |
| 299 |
/> |
| 300 |
) } |
| 301 |
{ isPanelVisible( settings, 'referrers' ) && ( |
| 302 |
<PerformanceReferrersPanel |
| 303 |
data={ postDetails as PerformanceData } |
| 304 |
isLoading={ loading } |
| 305 |
/> |
| 306 |
) } |
| 307 |
</> |
| 308 |
) } |
| 309 |
{ window.wpParselyPostUrl && ( |
| 310 |
<Button |
| 311 |
className="wp-parsely-view-post" |
| 312 |
variant="secondary" |
| 313 |
onClick={ () => { |
| 314 |
Telemetry.trackEvent( 'editor_sidebar_view_post_pressed' ); |
| 315 |
} } |
| 316 |
href={ window.wpParselyPostUrl } |
| 317 |
rel="noopener" |
| 318 |
target="_blank" |
| 319 |
> |
| 320 |
{ __( 'View this in Parse.ly', 'wp-parsely' ) } |
| 321 |
</Button> |
| 322 |
) } |
| 323 |
</div> |
| 324 |
); |
| 325 |
}; |
| 326 |
|