Skip to content

Commit

Permalink
ui: Add search block UI (#4444)
Browse files Browse the repository at this point in the history
* ui: Add block search feature

Signed-off-by: Shafiya Adzhani <[email protected]>

* fix: fis props on source view test

Signed-off-by: Shafiya Adzhani <[email protected]>

* ui: add build from search ui

Signed-off-by: Shafiya Adzhani <[email protected]>

* fix(search block): fix code reviews on pr

Signed-off-by: Shafiya Adzhani <[email protected]>

* build(search block): build assets

Signed-off-by: Shafiya Adzhani <[email protected]>

* docs(search block): add changelog

Signed-off-by: Shafiya Adzhani <[email protected]>
  • Loading branch information
adzshaf authored Aug 4, 2021
1 parent cd18b61 commit 948abc3
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 160 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ We use *breaking :warning:* to mark changes that are not backward compatible (re
- [#4453](https://github.com/thanos-io/thanos/pull/4453) Tools: Add flag `--selector.relabel-config-file` / `--selector.relabel-config` / `--max-time` / `--min-time` to filter served blocks.
- [#4482](https://github.com/thanos-io/thanos/pull/4482) COS: Add http_config for cos object store client.
- [#4487](https://github.com/thanos-io/thanos/pull/4487) Query: Add memcached auto discovery support.
- [#4444](https://github.com/thanos-io/thanos/pull/4444) UI: Add search block UI.

### Fixed

Expand Down
258 changes: 129 additions & 129 deletions pkg/ui/bindata.go

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/BlockSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { FC, ChangeEvent } from 'react';
import { Button, InputGroup, InputGroupAddon, InputGroupText, Input, Form } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import styles from './blocks.module.css';

interface BlockSearchInputProps {
onChange: ({ target }: ChangeEvent<HTMLInputElement>) => void;
onClick: () => void;
defaultValue: string;
}

export const BlockSearchInput: FC<BlockSearchInputProps> = ({ onChange, onClick, defaultValue }) => {
return (
<Form onSubmit={(e) => e.preventDefault()}>
<InputGroup className={styles.blockInput}>
<InputGroupAddon addonType="prepend">
<InputGroupText>
<FontAwesomeIcon icon={faSearch} />
</InputGroupText>
</InputGroupAddon>
<Input placeholder="Search block by ulid" onChange={onChange} defaultValue={defaultValue} />
<InputGroupAddon addonType="append">
<Button className="execute-btn" color="primary" onClick={onClick} type="submit">
Search
</Button>
</InputGroupAddon>
</InputGroup>
</Form>
);
};
71 changes: 45 additions & 26 deletions pkg/ui/react-app/src/thanos/pages/blocks/Blocks.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { FC, useMemo, useState } from 'react';
import React, { ChangeEvent, FC, useMemo, useState } from 'react';
import { RouteComponentProps } from '@reach/router';
import { UncontrolledAlert } from 'reactstrap';
import { useQueryParams, withDefault, NumberParam } from 'use-query-params';
import { useQueryParams, withDefault, NumberParam, StringParam } from 'use-query-params';
import { withStatusIndicator } from '../../../components/withStatusIndicator';
import { useFetch } from '../../../hooks/useFetch';
import PathPrefixProps from '../../../types/PathPrefixProps';
import { Block } from './block';
import { SourceView } from './SourceView';
import { BlockDetails } from './BlockDetails';
import { BlockSearchInput } from './BlockSearchInput';
import { sortBlocks } from './helpers';
import styles from './blocks.module.css';
import TimeRange from './TimeRange';

export interface BlockListProps {
blocks: Block[];
err: string | null;
Expand All @@ -21,6 +21,7 @@ export interface BlockListProps {

export const BlocksContent: FC<{ data: BlockListProps }> = ({ data }) => {
const [selectedBlock, selectBlock] = useState<Block>();
const [searchState, setSearchState] = useState<string>('');

const { blocks, label, err } = data;

Expand All @@ -42,47 +43,65 @@ export const BlocksContent: FC<{ data: BlockListProps }> = ({ data }) => {
return [0, 0];
}, [blocks, err]);

const [{ 'min-time': viewMinTime, 'max-time': viewMaxTime }, setQuery] = useQueryParams({
const [{ 'min-time': viewMinTime, 'max-time': viewMaxTime, ulid: blockSearchParam }, setQuery] = useQueryParams({
'min-time': withDefault(NumberParam, gridMinTime),
'max-time': withDefault(NumberParam, gridMaxTime),
ulid: withDefault(StringParam, ''),
});

const [blockSearch, setBlockSearch] = useState<string>(blockSearchParam);

const setViewTime = (times: number[]): void => {
setQuery({
'min-time': times[0],
'max-time': times[1],
});
};

const setBlockSearchInput = (searchState: string): void => {
setQuery({
ulid: searchState,
});
setBlockSearch(searchState);
};

if (err) return <UncontrolledAlert color="danger">{err.toString()}</UncontrolledAlert>;

return (
<>
{blocks.length > 0 ? (
<div className={styles.container}>
<div className={styles.grid}>
<div className={styles.sources}>
{Object.keys(blockPools).map((pk) => (
<SourceView
key={pk}
data={blockPools[pk]}
title={pk}
selectBlock={selectBlock}
gridMinTime={viewMinTime}
gridMaxTime={viewMaxTime}
/>
))}
<>
<BlockSearchInput
onChange={({ target }: ChangeEvent<HTMLInputElement>): void => setSearchState(target.value)}
onClick={() => setBlockSearchInput(searchState)}
defaultValue={blockSearchParam}
/>
<div className={styles.container}>
<div className={styles.grid}>
<div className={styles.sources}>
{Object.keys(blockPools).map((pk) => (
<SourceView
key={pk}
data={blockPools[pk]}
title={pk}
selectBlock={selectBlock}
gridMinTime={viewMinTime}
gridMaxTime={viewMaxTime}
blockSearch={blockSearch}
/>
))}
</div>
<TimeRange
gridMinTime={gridMinTime}
gridMaxTime={gridMaxTime}
viewMinTime={viewMinTime}
viewMaxTime={viewMaxTime}
onChange={setViewTime}
/>
</div>
<TimeRange
gridMinTime={gridMinTime}
gridMaxTime={gridMaxTime}
viewMinTime={viewMinTime}
viewMaxTime={viewMaxTime}
onChange={setViewTime}
/>
<BlockDetails selectBlock={selectBlock} block={selectedBlock} />
</div>
<BlockDetails selectBlock={selectBlock} block={selectedBlock} />
</div>
</>
) : (
<UncontrolledAlert color="warning">No blocks found.</UncontrolledAlert>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('Blocks SourceView', () => {
},
gridMinTime: 1596096000000,
gridMaxTime: 1595108031471,
blockSearch: '',
};

const sourceView = mount(<SourceView {...defaultProps} />);
Expand Down
18 changes: 13 additions & 5 deletions pkg/ui/react-app/src/thanos/pages/blocks/SourceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@ import React, { FC } from 'react';
import { Block, BlocksPool } from './block';
import { BlockSpan } from './BlockSpan';
import styles from './blocks.module.css';
import { getBlockByUlid } from './helpers';

export const BlocksRow: FC<{
blocks: Block[];
gridMinTime: number;
gridMaxTime: number;
selectBlock: React.Dispatch<React.SetStateAction<Block | undefined>>;
}> = ({ blocks, gridMinTime, gridMaxTime, selectBlock }) => {
blockSearch: string;
}> = ({ blocks, gridMinTime, gridMaxTime, selectBlock, blockSearch }) => {
const blockSearchValue = getBlockByUlid(blocks, blockSearch);

return (
<div className={styles.row}>
{blocks.map<JSX.Element>((b) => (
<BlockSpan selectBlock={selectBlock} block={b} gridMaxTime={gridMaxTime} gridMinTime={gridMinTime} key={b.ulid} />
))}
{blockSearchValue.map<JSX.Element>((b) => {
return (
<BlockSpan selectBlock={selectBlock} block={b} gridMaxTime={gridMaxTime} gridMinTime={gridMinTime} key={b.ulid} />
);
})}
</div>
);
};
Expand All @@ -24,9 +30,10 @@ export interface SourceViewProps {
gridMinTime: number;
gridMaxTime: number;
selectBlock: React.Dispatch<React.SetStateAction<Block | undefined>>;
blockSearch: string;
}

export const SourceView: FC<SourceViewProps> = ({ data, title, gridMaxTime, gridMinTime, selectBlock }) => {
export const SourceView: FC<SourceViewProps> = ({ data, title, gridMaxTime, gridMinTime, selectBlock, blockSearch }) => {
return (
<>
<div className={styles.source}>
Expand All @@ -43,6 +50,7 @@ export const SourceView: FC<SourceViewProps> = ({ data, title, gridMaxTime, grid
key={`${k}-${i}`}
gridMaxTime={gridMaxTime}
gridMinTime={gridMinTime}
blockSearch={blockSearch}
/>
))}
</React.Fragment>
Expand Down
4 changes: 4 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/blocks.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,7 @@
.level-6 {
background: var(--level-6);
}

.blockInput {
margin-bottom: 12px;
}
17 changes: 17 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LabelSet, Block, BlocksPool } from './block';
import { Fuzzy, FuzzyResult } from '@nexucis/fuzzy';

const stringify = (map: LabelSet): string => {
let t = '';
Expand Down Expand Up @@ -103,3 +104,19 @@ export const download = (blob: Block): string => {

return url;
};

export const getBlockByUlid = (blocks: Block[], ulid: string): Block[] => {
if (ulid === '') {
return blocks;
}

const ulidArray = blocks.map((block) => block.ulid);
const fuz = new Fuzzy({ caseSensitive: true });

const result: FuzzyResult[] = fuz.filter(ulid, ulidArray);

const resultIndex = result.map((value) => value.index);

const blockResult = blocks.filter((block, index) => resultIndex.includes(index));
return blockResult;
};

0 comments on commit 948abc3

Please sign in to comment.