Skip to content

Commit

Permalink
ui: Migrate block viewer to React
Browse files Browse the repository at this point in the history
  • Loading branch information
onprem committed Aug 4, 2020
1 parent 040b69b commit 13207b6
Show file tree
Hide file tree
Showing 12 changed files with 481 additions and 64 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ $(REACT_APP_NODE_MODULES_PATH): $(REACT_APP_PATH)/package.json $(REACT_APP_PATH)

$(REACT_APP_OUTPUT_DIR): $(REACT_APP_NODE_MODULES_PATH) $(REACT_APP_SOURCE_FILES)
@echo ">> building React app"
@./scripts/build-react-app.sh
@scripts/build-react-app.sh

.PHONY: assets
assets: # Repacks all static assets into go file for easier deploy.
Expand Down
122 changes: 61 additions & 61 deletions pkg/ui/bindata.go

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion pkg/ui/react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { Alerts, Config, Flags, Rules, ServiceDiscovery, Status, Targets, TSDBSt
import PathPrefixProps from './types/PathPrefixProps';
import ThanosComponentProps from './thanos/types/ThanosComponentProps';
import Navigation from './thanos/Navbar';
import { Stores, ErrorBoundary } from './thanos/pages';
import { Stores, ErrorBoundary, Blocks } from './thanos/pages';

import './App.css';

const defaultRouteConfig: { [component: string]: string } = {
query: '/graph',
rule: '/alerts',
bucket: '/blocks',
compact: '/blocks',
};

const App: FC<PathPrefixProps & ThanosComponentProps> = ({ pathPrefix, thanosComponent }) => {
Expand Down Expand Up @@ -41,6 +43,7 @@ const App: FC<PathPrefixProps & ThanosComponentProps> = ({ pathPrefix, thanosCom
<TSDBStatus path="/tsdb-status" pathPrefix={pathPrefix} />
<Targets path="/targets" pathPrefix={pathPrefix} />
<Stores path="/stores" pathPrefix={pathPrefix} />
<Blocks path="/blocks" pathPrefix={pathPrefix} />
</Router>
</Container>
</ErrorBoundary>
Expand Down
2 changes: 2 additions & 0 deletions pkg/ui/react-app/src/thanos/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const navConfig: { [component: string]: (NavConfig | NavDropDown)[] } = {
{ name: 'Alerts', uri: '/new/alerts' },
{ name: 'Rules', uri: '/new/rules' },
],
bucket: [{ name: 'Blocks', uri: '/new/blocks' }],
compact: [{ name: 'Blocks', uri: '/new/blocks' }],
};

interface NavigationProps {
Expand Down
70 changes: 70 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/BlockDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { FC } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { Block } from './block';
import styles from './blocks.module.css';
import moment from 'moment';

interface BlockDetailsProps {
block: Block | undefined;
selectBlock: React.Dispatch<React.SetStateAction<Block | undefined>>;
}

export const BlockDetails: FC<BlockDetailsProps> = ({ block, selectBlock }) => {
return (
<div className={`${styles.blockDetails} ${block && styles.open}`}>
{block && (
<>
<div className={styles.detailsTop}>
<p>{block.ulid}</p>
<button className={styles.closeBtn} onClick={(): void => selectBlock(undefined)}>
<FontAwesomeIcon icon={faTimes} />
</button>
</div>
<hr />
<div>
<b>Start Time:</b> {moment.unix(block.minTime / 1000).format('LLL')}
</div>
<div>
<b>End Time:</b> {moment.unix(block.maxTime / 1000).format('LLL')}
</div>
<div>
<b>Duration:</b> {moment.duration(block.maxTime - block.minTime, 'ms').humanize()}
</div>
<hr />
<div>
<b>Series:</b> {block.stats.numSeries}
</div>
<div>
<b>Samples:</b> {block.stats.numSamples}
</div>
<div>
<b>Chunks:</b> {block.stats.numChunks}
</div>
<hr />
<div>
<b>Resolution:</b> {block.thanos.downsample.resolution}
</div>
<div>
<b>Level:</b> {block.compaction.level}
</div>
<div>
<b>Source:</b> {block.thanos.source}
</div>
<hr />
<div>
<b>Labels:</b>
<ul>
{Object.entries(block.thanos.labels).map(([key, value]) => (
<li key={key}>
<b>{key}: </b>
{value}
</li>
))}
</ul>
</div>
</>
)}
</div>
);
};
27 changes: 27 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/BlockSpan.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { FC } from 'react';
import { Block } from './block';
import styles from './blocks.module.css';

interface BlockSpanProps {
block: Block;
gridMinTime: number;
gridMaxTime: number;
selectBlock: React.Dispatch<React.SetStateAction<Block | undefined>>;
}

export const BlockSpan: FC<BlockSpanProps> = ({ block, gridMaxTime, gridMinTime, selectBlock }) => {
const viewWidth = gridMaxTime - gridMinTime;
const spanWidth = ((block.maxTime - block.minTime) / viewWidth) * 100;
const spanOffset = ((block.minTime - gridMinTime) / viewWidth) * 100;

return (
<button
onClick={(): void => selectBlock(block)}
className={styles.blockSpan}
style={{
width: `${spanWidth}%`,
left: `${spanOffset}%`,
}}
/>
);
};
81 changes: 81 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/Blocks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { FC, useMemo, useState } from 'react';
import { RouteComponentProps } from '@reach/router';
import { Alert } from 'reactstrap';
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 { sortBlocks } from './helpers';
import styles from './blocks.module.css';

export interface BlockListProps {
blocks: Block[];
err: string | null;
label: string;
refreshedAt: string;
}

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

const { blocks, label } = data;

const blockPools = useMemo(() => sortBlocks(blocks, label), [blocks, label]);
const [gridMinTime, gridMaxTime] = useMemo(() => {
let gridMinTime = blocks[0].minTime;
let gridMaxTime = blocks[0].maxTime;
blocks.forEach(block => {
if (block.minTime < gridMinTime) {
gridMinTime = block.minTime;
}
if (block.maxTime > gridMaxTime) {
gridMaxTime = block.maxTime;
}
});
return [gridMinTime, gridMaxTime];
}, [blocks]);

return (
<>
{blocks.length > 0 ? (
<div className={styles.container}>
<div className={styles.grid}>
{Object.keys(blockPools).map(pk => (
<SourceView
key={pk}
data={blockPools[pk]}
title={pk}
selectBlock={selectBlock}
gridMinTime={gridMinTime}
gridMaxTime={gridMaxTime}
/>
))}
</div>
<BlockDetails selectBlock={selectBlock} block={selectedBlock} />
</div>
) : (
<Alert color="warning">No blocks found.</Alert>
)}
</>
);
};

const BlocksWithStatusIndicator = withStatusIndicator(BlocksContent);

export const Blocks: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix = '' }) => {
const { response, error, isLoading } = useFetch<BlockListProps>(`${pathPrefix}/api/v1/blocks`);
const { status: responseStatus } = response;
const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching';

return (
<BlocksWithStatusIndicator
data={response.data}
error={badResponse ? new Error(responseStatus) : error}
isLoading={isLoading}
/>
);
};

export default Blocks;
49 changes: 49 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/SourceView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { FC } from 'react';
import { Block, BlocksPool } from './block';
import { BlockSpan } from './BlockSpan';
import styles from './blocks.module.css';

const BlocksRow: FC<{
blocks: Block[];
gridMinTime: number;
gridMaxTime: number;
selectBlock: React.Dispatch<React.SetStateAction<Block | undefined>>;
}> = ({ blocks, gridMinTime, gridMaxTime, selectBlock }) => {
return (
<div className={styles.row}>
{blocks.map<JSX.Element>(b => (
<BlockSpan selectBlock={selectBlock} block={b} gridMaxTime={gridMaxTime} gridMinTime={gridMinTime} key={b.ulid} />
))}
</div>
);
};

export const SourceView: FC<{
data: BlocksPool;
title: string;
gridMinTime: number;
gridMaxTime: number;
selectBlock: React.Dispatch<React.SetStateAction<Block | undefined>>;
}> = ({ data, title, gridMaxTime, gridMinTime, selectBlock }) => {
return (
<>
<div className={styles.source}>
<div className={styles.title}>
<h4>{title}</h4>
</div>
<div className={styles.rowsContainer}>
{Object.keys(data).map(k => (
<BlocksRow
selectBlock={selectBlock}
blocks={data[k]}
key={k}
gridMaxTime={gridMaxTime}
gridMinTime={gridMinTime}
/>
))}
</div>
</div>
<hr />
</>
);
};
35 changes: 35 additions & 0 deletions pkg/ui/react-app/src/thanos/pages/blocks/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export interface Block {
compaction: {
level: number;
sources: string[];
parents?: {
maxTime: number;
minTime: number;
ulid: string;
}[];
};
maxTime: number;
minTime: number;
stats: {
numChunks: number;
numSamples: number;
numSeries: number;
};
thanos: {
downsample: {
resolution: number;
};
labels: LabelSet;
source: string;
};
ulid: string;
version: number;
}

export interface LabelSet {
[labelName: string]: string;
}

export interface BlocksPool {
[key: string]: Block[];
}
Loading

0 comments on commit 13207b6

Please sign in to comment.