github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/shared/components/loading/index.tsx (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 import React, { ReactNode } from "react"; 12 import { RequestError } from "src/util/api"; 13 import spinner from "assets/spinner.gif"; 14 import { adminUIAccess } from "src/util/docs"; 15 import "./index.styl"; 16 17 interface LoadingProps { 18 loading: boolean; 19 error?: Error | Error[] | null; 20 className?: string; 21 image?: string; 22 render: () => any; 23 } 24 25 /** 26 * getValidErrorsList eliminates any null Error values, and returns either 27 * null or a non-empty list of Errors. 28 */ 29 function getValidErrorsList (errors?: Error | Error[] | null): Error[] | null { 30 if (errors) { 31 if (!Array.isArray(errors)) { 32 // Put single Error into a list to simplify logic in main Loading component. 33 return [errors]; 34 } else { 35 // Remove null values from Error[]. 36 const validErrors = errors.filter(e => !!e); 37 if (validErrors.length === 0) { 38 return null; 39 } 40 return validErrors; 41 } 42 } 43 return null; 44 } 45 46 /** 47 * getDetails produces a hint for the given error object. 48 */ 49 function getDetails (error: Error): ReactNode { 50 if (error instanceof RequestError) { 51 if (error.status === 403) { 52 return ( 53 <p> 54 Insufficient privileges to view this resource. <a href={adminUIAccess} target="_blank"> 55 Learn more 56 </a> 57 </p> 58 ); 59 } 60 } 61 return <p>no details available</p>; 62 } 63 64 /** 65 * Loading will display a background image instead of the content if the 66 * loading prop is true. 67 */ 68 export default function Loading(props: LoadingProps) { 69 const className = props.className || "loading-image loading-image__spinner"; 70 const imageURL = props.image || spinner; 71 const image = { 72 "backgroundImage": `url(${imageURL})`, 73 }; 74 75 const errors = getValidErrorsList(props.error); 76 77 // Check for `error` before `loading`, since tests for `loading` often return 78 // true even if CachedDataReducer has an error and is no longer really "loading". 79 if (errors) { 80 const errorCountMessage = (errors.length > 1) ? "Multiple errors occurred" : "An error was encountered"; 81 return ( 82 <div className="loading-error"> 83 <p>{errorCountMessage} while loading this data:</p> 84 <ul> 85 {errors.map((error, idx) => ( 86 <li key={idx}><b>{error.message}</b> 87 {getDetails(error)}</li> 88 ))} 89 </ul> 90 </div> 91 ); 92 } 93 if (props.loading) { 94 return <div className={className} style={image} />; 95 } 96 return props.render(); 97 }