github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/thanos/pages/blocks/Blocks.tsx (about)

     1  import React, { ChangeEvent, FC, useMemo, useState } from 'react';
     2  import { RouteComponentProps } from '@reach/router';
     3  import { UncontrolledAlert } from 'reactstrap';
     4  import { useQueryParams, withDefault, NumberParam, StringParam, BooleanParam } from 'use-query-params';
     5  import { withStatusIndicator } from '../../../components/withStatusIndicator';
     6  import { useFetch } from '../../../hooks/useFetch';
     7  import PathPrefixProps from '../../../types/PathPrefixProps';
     8  import { Block } from './block';
     9  import { SourceView } from './SourceView';
    10  import { BlockDetails } from './BlockDetails';
    11  import { BlockSearchInput } from './BlockSearchInput';
    12  import { BlockFilterCompaction } from './BlockFilterCompaction';
    13  import { sortBlocks, getBlockByUlid, getFilteredBlockPools } from './helpers';
    14  import styles from './blocks.module.css';
    15  import TimeRange from './TimeRange';
    16  import Checkbox from '../../../components/Checkbox';
    17  
    18  export interface BlockListProps {
    19    blocks: Block[];
    20    err: string | null;
    21    label: string;
    22    refreshedAt: string;
    23  }
    24  
    25  export const BlocksContent: FC<{ data: BlockListProps }> = ({ data }) => {
    26    const [selectedBlock, selectBlock] = useState<Block>();
    27    const [searchState, setSearchState] = useState<string>('');
    28  
    29    const { blocks, label, err } = data;
    30  
    31    const [gridMinTime, gridMaxTime] = useMemo(() => {
    32      if (!err && blocks.length > 0) {
    33        let gridMinTime = blocks[0].minTime;
    34        let gridMaxTime = blocks[0].maxTime;
    35        blocks.forEach((block) => {
    36          if (block.minTime < gridMinTime) {
    37            gridMinTime = block.minTime;
    38          }
    39          if (block.maxTime > gridMaxTime) {
    40            gridMaxTime = block.maxTime;
    41          }
    42        });
    43        return [gridMinTime, gridMaxTime];
    44      }
    45      return [0, 0];
    46    }, [blocks, err]);
    47  
    48    const [
    49      {
    50        'min-time': viewMinTime,
    51        'max-time': viewMaxTime,
    52        ulid: blockSearchParam,
    53        'find-overlapping': findOverlappingParam,
    54        'filter-compaction': filterCompactionParam,
    55        'compaction-level': compactionLevelParam,
    56      },
    57      setQuery,
    58    ] = useQueryParams({
    59      'min-time': withDefault(NumberParam, gridMinTime),
    60      'max-time': withDefault(NumberParam, gridMaxTime),
    61      ulid: withDefault(StringParam, ''),
    62      'find-overlapping': withDefault(BooleanParam, false),
    63      'filter-compaction': withDefault(BooleanParam, false),
    64      'compaction-level': withDefault(NumberParam, 0),
    65    });
    66  
    67    const [filterCompaction, setFilterCompaction] = useState<boolean>(filterCompactionParam);
    68    const [findOverlappingBlocks, setFindOverlappingBlocks] = useState<boolean>(findOverlappingParam);
    69    const [compactionLevel, setCompactionLevel] = useState<number>(compactionLevelParam);
    70    const [compactionLevelInput, setCompactionLevelInput] = useState<string>(compactionLevelParam.toString());
    71    const [blockSearch, setBlockSearch] = useState<string>(blockSearchParam);
    72  
    73    const blockPools = useMemo(() => sortBlocks(blocks, label, findOverlappingBlocks), [blocks, label, findOverlappingBlocks]);
    74    const filteredBlocks = useMemo(() => getBlockByUlid(blocks, blockSearch), [blocks, blockSearch]);
    75    const filteredBlockPools = useMemo(() => getFilteredBlockPools(blockPools, filteredBlocks), [filteredBlocks, blockPools]);
    76  
    77    const setViewTime = (times: number[]): void => {
    78      setQuery({
    79        'min-time': times[0],
    80        'max-time': times[1],
    81      });
    82    };
    83  
    84    const setBlockSearchInput = (searchState: string): void => {
    85      setQuery({
    86        ulid: searchState,
    87      });
    88      setBlockSearch(searchState);
    89    };
    90  
    91    const onChangeCompactionCheckbox = (target: EventTarget & HTMLInputElement) => {
    92      setFilterCompaction(target.checked);
    93      if (target.checked) {
    94        const compactionLevel: number = parseInt(compactionLevelInput);
    95        setQuery({
    96          'filter-compaction': target.checked,
    97          'compaction-level': compactionLevel,
    98        });
    99        setCompactionLevel(compactionLevel);
   100      } else {
   101        setQuery({
   102          'filter-compaction': target.checked,
   103          'compaction-level': 0,
   104        });
   105        setCompactionLevel(0);
   106      }
   107    };
   108  
   109    const onChangeCompactionInput = (target: HTMLInputElement) => {
   110      if (filterCompaction) {
   111        setQuery({
   112          'compaction-level': parseInt(target.value),
   113        });
   114        setCompactionLevel(parseInt(target.value));
   115      }
   116      setCompactionLevelInput(target.value);
   117    };
   118  
   119    if (err) return <UncontrolledAlert color="danger">{err.toString()}</UncontrolledAlert>;
   120  
   121    return (
   122      <>
   123        {blocks.length > 0 ? (
   124          <>
   125            <BlockSearchInput
   126              onChange={({ target }: ChangeEvent<HTMLInputElement>): void => setSearchState(target.value)}
   127              onClick={() => setBlockSearchInput(searchState)}
   128              defaultValue={blockSearchParam}
   129            />
   130            <div className={styles.blockFilter}>
   131              <Checkbox
   132                id="find-overlap-block-checkbox"
   133                onChange={({ target }) => {
   134                  setQuery({
   135                    'find-overlapping': target.checked,
   136                  });
   137                  setFindOverlappingBlocks(target.checked);
   138                }}
   139                defaultChecked={findOverlappingBlocks}
   140              >
   141                Enable finding overlapping blocks
   142              </Checkbox>
   143              <BlockFilterCompaction
   144                id="filter-compaction-checkbox"
   145                defaultChecked={filterCompaction}
   146                onChangeCheckbox={({ target }) => onChangeCompactionCheckbox(target)}
   147                onChangeInput={({ target }: ChangeEvent<HTMLInputElement>): void => {
   148                  onChangeCompactionInput(target);
   149                }}
   150                defaultValue={compactionLevelInput}
   151              />
   152            </div>
   153            <div className={styles.container}>
   154              <div className={styles.grid}>
   155                <div className={styles.sources}>
   156                  {Object.keys(filteredBlockPools).length > 0 ? (
   157                    Object.keys(filteredBlockPools).map((pk) => (
   158                      <SourceView
   159                        key={pk}
   160                        data={filteredBlockPools[pk]}
   161                        title={pk}
   162                        selectBlock={selectBlock}
   163                        gridMinTime={viewMinTime}
   164                        gridMaxTime={viewMaxTime}
   165                        blockSearch={blockSearch}
   166                        compactionLevel={compactionLevel}
   167                      />
   168                    ))
   169                  ) : (
   170                    <div>
   171                      <h3>No Blocks Found!</h3>
   172                    </div>
   173                  )}
   174                </div>
   175                <TimeRange
   176                  gridMinTime={gridMinTime}
   177                  gridMaxTime={gridMaxTime}
   178                  viewMinTime={viewMinTime}
   179                  viewMaxTime={viewMaxTime}
   180                  onChange={setViewTime}
   181                />
   182              </div>
   183              <BlockDetails selectBlock={selectBlock} block={selectedBlock} />
   184            </div>
   185          </>
   186        ) : (
   187          <UncontrolledAlert color="warning">No blocks found.</UncontrolledAlert>
   188        )}
   189      </>
   190    );
   191  };
   192  
   193  const BlocksWithStatusIndicator = withStatusIndicator(BlocksContent);
   194  
   195  interface BlocksProps {
   196    view?: string;
   197  }
   198  
   199  export const Blocks: FC<RouteComponentProps & PathPrefixProps & BlocksProps> = ({ pathPrefix = '', view = 'global' }) => {
   200    const { response, error, isLoading } = useFetch<BlockListProps>(
   201      `${pathPrefix}/api/v1/blocks${view ? '?view=' + view : ''}`
   202    );
   203    const { status: responseStatus } = response;
   204    const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching';
   205  
   206    return (
   207      <BlocksWithStatusIndicator
   208        data={response.data}
   209        error={badResponse ? new Error(responseStatus) : error}
   210        isLoading={isLoading}
   211      />
   212    );
   213  };
   214  
   215  export default Blocks;