Commit f17a0e17 authored by Chris Marchbanks's avatar Chris Marchbanks Committed by Julius Volz
Browse files

Implement the /flags page in react (#6248)



* Implement the /flags page in react
Signed-off-by: default avatarChris Marchbanks <csmarchbanks@gmail.com>

* Use custom react hook for calling api
Signed-off-by: default avatarChris Marchbanks <csmarchbanks@gmail.com>
parent 74726367
......@@ -64,9 +64,12 @@
"@types/flot": "0.0.31",
"@types/moment-timezone": "^0.5.10",
"@types/reactstrap": "^8.0.5",
"@types/sinon": "^7.5.0",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"babel-eslint": "10.x",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"eslint": "6.x",
"eslint-config-prettier": "^6.4.0",
"eslint-config-react-app": "^5.0.2",
......@@ -76,10 +79,8 @@
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "1.x",
"jest-fetch-mock": "^2.1.2",
"prettier": "^1.18.2",
"@types/sinon": "^7.5.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"sinon": "^7.5.0"
},
"proxy": "http://localhost:9090"
......
import React, { FC, useEffect, useState } from 'react';
import React, { FC, useState } from 'react';
import { RouteComponentProps } from '@reach/router';
import { Alert, Button } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import CopyToClipboard from 'react-copy-to-clipboard';
import { useFetch } from '../utils/useFetch';
import './Config.css';
const Config: FC<RouteComponentProps> = () => {
const [config, setConfig] = useState(null);
const [error, setError] = useState('');
const { response, error } = useFetch('../api/v1/status/config');
const [copied, setCopied] = useState(false);
useEffect(() => {
fetch('../api/v1/status/config')
.then(res => res.json())
.then(res => setConfig(res.data.yaml))
.catch(error => setError(error.message));
}, []);
const config = response && response.data.yaml;
return (
<>
<h2>
......@@ -38,7 +32,7 @@ const Config: FC<RouteComponentProps> = () => {
{error ? (
<Alert color="danger">
<strong>Error:</strong> Error fetching configuration: {error}
<strong>Error:</strong> Error fetching configuration: {error.message}
</Alert>
) : config ? (
<pre className="config-yaml">{config}</pre>
......
import * as React from 'react';
import { mount, shallow, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import Flags, { FlagMap } from './Flags';
import { Alert, Table } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
const sampleFlagsResponse: {
status: string;
data: FlagMap;
} = {
status: 'success',
data: {
'alertmanager.notification-queue-capacity': '10000',
'alertmanager.timeout': '10s',
'config.file': './documentation/examples/prometheus.yml',
'log.format': 'logfmt',
'log.level': 'info',
'query.lookback-delta': '5m',
'query.max-concurrency': '20',
'query.max-samples': '50000000',
'query.timeout': '2m',
'rules.alert.for-grace-period': '10m',
'rules.alert.for-outage-tolerance': '1h',
'rules.alert.resend-delay': '1m',
'storage.remote.flush-deadline': '1m',
'storage.remote.read-concurrent-limit': '10',
'storage.remote.read-max-bytes-in-frame': '1048576',
'storage.remote.read-sample-limit': '50000000',
'storage.tsdb.allow-overlapping-blocks': 'false',
'storage.tsdb.max-block-duration': '36h',
'storage.tsdb.min-block-duration': '2h',
'storage.tsdb.no-lockfile': 'false',
'storage.tsdb.path': 'data/',
'storage.tsdb.retention': '0s',
'storage.tsdb.retention.size': '0B',
'storage.tsdb.retention.time': '0s',
'storage.tsdb.wal-compression': 'false',
'storage.tsdb.wal-segment-size': '0B',
'web.console.libraries': 'console_libraries',
'web.console.templates': 'consoles',
'web.cors.origin': '.*',
'web.enable-admin-api': 'false',
'web.enable-lifecycle': 'false',
'web.external-url': '',
'web.listen-address': '0.0.0.0:9090',
'web.max-connections': '512',
'web.page-title': 'Prometheus Time Series Collection and Processing Server',
'web.read-timeout': '5m',
'web.route-prefix': '/',
'web.user-assets': '',
},
};
describe('Flags', () => {
beforeEach(() => {
fetch.resetMocks();
});
describe('before data is returned', () => {
it('renders a spinner', () => {
const flags = shallow(<Flags />);
const icon = flags.find(FontAwesomeIcon);
expect(icon.prop('icon')).toEqual(faSpinner);
expect(icon.prop('spin')).toEqual(true);
});
});
describe('when data is returned', () => {
it('renders a table', async () => {
const mock = fetch.mockResponse(JSON.stringify(sampleFlagsResponse));
let flags: ReactWrapper;
await act(async () => {
flags = mount(<Flags />);
});
flags.update();
expect(mock).toHaveBeenCalledWith('../api/v1/status/flags', undefined);
const table = flags.find(Table);
expect(table.prop('striped')).toBe(true);
const rows = flags.find('tr');
const keys = Object.keys(sampleFlagsResponse.data);
expect(rows.length).toBe(keys.length);
for (let i = 0; i < keys.length; i++) {
const row = rows.at(i);
expect(row.find('th').text()).toBe(keys[i]);
expect(row.find('td').text()).toBe(sampleFlagsResponse.data[keys[i]]);
}
});
});
describe('when an error is returned', () => {
it('displays an alert', async () => {
const mock = fetch.mockReject(new Error('error loading flags'));
let flags: ReactWrapper;
await act(async () => {
flags = mount(<Flags />);
});
flags.update();
expect(mock).toHaveBeenCalledWith('../api/v1/status/flags', undefined);
const alert = flags.find(Alert);
expect(alert.prop('color')).toBe('danger');
expect(alert.text()).toContain('error loading flags');
});
});
});
import React, { FC } from 'react';
import { RouteComponentProps } from '@reach/router';
import { Alert, Table } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { useFetch } from '../utils/useFetch';
const Flags: FC<RouteComponentProps> = () => <div>Flags page</div>;
export interface FlagMap {
[key: string]: string;
}
const Flags: FC<RouteComponentProps> = () => {
const { response, error } = useFetch('../api/v1/status/flags');
const body = () => {
const flags: FlagMap = response && response.data;
if (error) {
return (
<Alert color="danger">
<strong>Error:</strong> Error fetching flags: {error.message}
</Alert>
);
} else if (flags) {
return (
<Table bordered={true} size="sm" striped={true}>
<tbody>
{Object.keys(flags).map(key => {
return (
<tr key={key}>
<th>{key}</th>
<td>{flags[key]}</td>
</tr>
);
})}
</tbody>
</Table>
);
}
return <FontAwesomeIcon icon={faSpinner} spin />;
};
return (
<>
<h2>Command-Line Flags</h2>
{body()}
</>
);
};
export default Flags;
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { GlobalWithFetchMock } from 'jest-fetch-mock';
import './globals';
configure({ adapter: new Adapter() });
const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
customGlobal.fetch = require('jest-fetch-mock');
customGlobal.fetchMock = customGlobal.fetch;
import { useState, useEffect } from 'react';
export const useFetch = (url: string, options?: RequestInit) => {
const [response, setResponse] = useState();
const [error, setError] = useState();
const [isLoading, setIsLoading] = useState();
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(url, options);
if (!res.ok) {
throw new Error(res.statusText);
}
const json = await res.json();
setResponse(json);
setIsLoading(false);
} catch (error) {
setError(error);
}
};
fetchData();
}, [url, options]);
return { response, error, isLoading };
};
......@@ -3195,6 +3195,14 @@ create-react-context@^0.3.0:
gud "^1.0.0"
warning "^4.0.3"
cross-fetch@^2.2.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.3.tgz#e8a0b3c54598136e037f8650f8e823ccdfac198e"
integrity sha512-PrWWNH3yL2NYIb/7WF/5vFG3DCQiXDOVf8k3ijatbrtnwNuhMWLC7YF7uqf53tbTFDzHIUD8oITw4Bxt8ST3Nw==
dependencies:
node-fetch "2.1.2"
whatwg-fetch "2.0.4"
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
......@@ -6024,6 +6032,14 @@ jest-environment-node@^24.9.0:
jest-mock "^24.9.0"
jest-util "^24.9.0"
jest-fetch-mock@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-2.1.2.tgz#1260b347918e3931c4ec743ceaf60433da661bd0"
integrity sha512-tcSR4Lh2bWLe1+0w/IwvNxeDocMI/6yIA2bijZ0fyWxC4kQ18lckQ1n7Yd40NKuisGmcGBRFPandRXrW/ti/Bw==
dependencies:
cross-fetch "^2.2.2"
promise-polyfill "^7.1.1"
jest-get-type@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
......@@ -7230,6 +7246,11 @@ no-case@^2.2.0:
dependencies:
lower-case "^1.1.1"
node-fetch@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
......@@ -8700,6 +8721,11 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
promise-polyfill@^7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b"
integrity sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==
promise@8.0.3:
version "8.0.3"
resolved "https://registry.yarnpkg.com/promise/-/promise-8.0.3.tgz#f592e099c6cddc000d538ee7283bb190452b0bf6"
......@@ -11018,6 +11044,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment