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  }