mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Update React 16->17, TypeScript, and some other node deps
This updates React, TypeScript, and some other node packages (but not everything). A couple of notes: - `enzyme-adapter-react-16` does not have a React 17 equivalent yet, so I switched to the fork `@wojtekmaj/enzyme-adapter-react-17` - A bunch of tests are still failing because I think in the enzyme testing environment, a browser API (`ResizeObserver`) is missing, and maybe for other reasons. This needs to be explored + fixed. - The TypeScript update introduced more stringent rules, which required fixing up a bunch of pieces of code a bit. - The `use-media` package doesn't work with React 17 yet, so I just built our own minimal `useMedia` hook instead (just a couple of lines). - I commented out part of the code in `withStartingIndicator.tsx` because it fails the now-stricter lint checks. It needs to be fixed (and not commented out). Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
parent
dff78eb508
commit
ff2d297b0a
|
@ -6,7 +6,6 @@
|
|||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/camelcase": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": ["off"],
|
||||
"eol-last": [
|
||||
"error",
|
||||
|
@ -28,5 +27,6 @@
|
|||
},
|
||||
"plugins": [
|
||||
"prettier"
|
||||
]
|
||||
],
|
||||
"ignorePatterns": ["src/vendor/**"]
|
||||
}
|
||||
|
|
37634
web/ui/react-app/package-lock.json
generated
37634
web/ui/react-app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -24,28 +24,27 @@
|
|||
"codemirror-promql": "^0.17.0",
|
||||
"css.escape": "^1.5.1",
|
||||
"downshift": "^3.4.8",
|
||||
"enzyme-to-json": "^3.4.3",
|
||||
"enzyme-to-json": "^3.6.2",
|
||||
"i": "^0.3.6",
|
||||
"jquery": "^3.5.1",
|
||||
"jquery.flot.tooltip": "^0.9.0",
|
||||
"jsdom": "^16.4.0",
|
||||
"jsdom": "^17.0.0",
|
||||
"moment": "^2.24.0",
|
||||
"moment-timezone": "^0.5.23",
|
||||
"popper.js": "^1.14.3",
|
||||
"react": "^16.7.0",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-resize-detector": "^5.0.7",
|
||||
"react": "^17.0.2",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-resize-detector": "^6.7.6",
|
||||
"react-router-dom": "^5.2.1",
|
||||
"react-scripts": "3.4.4",
|
||||
"react-test-renderer": "^16.9.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"reactstrap": "^8.9.0",
|
||||
"sanitize-html": "^2.3.3",
|
||||
"sass": "1.32.10",
|
||||
"tempusdominus-bootstrap-4": "^5.1.2",
|
||||
"tempusdominus-core": "^5.0.3",
|
||||
"typescript": "^3.3.3",
|
||||
"use-media": "^1.4.0"
|
||||
"typescript": "^4.4.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -68,39 +67,38 @@
|
|||
"not op_mini all"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@testing-library/react-hooks": "^3.1.1",
|
||||
"@types/enzyme": "^3.10.3",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.5",
|
||||
"@testing-library/react-hooks": "^7.0.1",
|
||||
"@types/enzyme": "^3.10.9",
|
||||
"@types/flot": "0.0.31",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/jquery": "^3.5.1",
|
||||
"@types/moment-timezone": "^0.5.10",
|
||||
"@types/node": "^12.11.1",
|
||||
"@types/react": "^16.8.2",
|
||||
"@types/react-copy-to-clipboard": "^5.0.0",
|
||||
"@types/react-dom": "^16.8.0",
|
||||
"@types/node": "^16.7.6",
|
||||
"@types/react": "^17.0.19",
|
||||
"@types/react-copy-to-clipboard": "^5.0.1",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-resize-detector": "^5.0.0",
|
||||
"@types/react-router-dom": "^5.1.8",
|
||||
"@types/reactstrap": "^8.7.2",
|
||||
"@types/sanitize-html": "^1.20.2",
|
||||
"@types/sinon": "^9.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "2.x",
|
||||
"@typescript-eslint/parser": "2.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",
|
||||
"eslint-plugin-flowtype": "4.x",
|
||||
"@types/sinon": "^10.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "4.x",
|
||||
"@typescript-eslint/parser": "4.x",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.3",
|
||||
"enzyme": "^3.11.0",
|
||||
"eslint": "7.x",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-react-app": "^6.0.0",
|
||||
"eslint-plugin-flowtype": "5.x",
|
||||
"eslint-plugin-import": "2.x",
|
||||
"eslint-plugin-jsx-a11y": "6.x",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-react": "7.x",
|
||||
"eslint-plugin-react-hooks": "2.x",
|
||||
"eslint-plugin-react-hooks": "4.x",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"mutationobserver-shim": "^0.3.7",
|
||||
"prettier": "^1.18.2",
|
||||
"sinon": "^9.0.3"
|
||||
"prettier": "^2.3.2",
|
||||
"sinon": "^11.1.2"
|
||||
},
|
||||
"proxy": "http://localhost:9090",
|
||||
"jest": {
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('App', () => {
|
|||
TargetsPage,
|
||||
TSDBStatusPage,
|
||||
PanelListPage,
|
||||
].forEach(component => {
|
||||
].forEach((component) => {
|
||||
const c = app.find(component);
|
||||
expect(c).toHaveLength(1);
|
||||
});
|
||||
|
|
|
@ -3,7 +3,6 @@ import Navigation from './Navbar';
|
|||
import { Container } from 'reactstrap';
|
||||
|
||||
import { BrowserRouter as Router, Redirect, Switch, Route } from 'react-router-dom';
|
||||
import useMedia from 'use-media';
|
||||
import {
|
||||
AlertsPage,
|
||||
ConfigPage,
|
||||
|
@ -19,6 +18,7 @@ import { PathPrefixContext } from './contexts/PathPrefixContext';
|
|||
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
|
||||
import { Theme, themeLocalStorageKey } from './Theme';
|
||||
import { useLocalStorage } from './hooks/useLocalStorage';
|
||||
import useMedia from './hooks/useMedia';
|
||||
|
||||
interface AppProps {
|
||||
consolesLink: string | null;
|
||||
|
|
|
@ -8,7 +8,7 @@ const MockCmp: React.FC = () => <div className="mock" />;
|
|||
describe('Checkbox', () => {
|
||||
it('renders with subcomponents', () => {
|
||||
const checkBox = shallow(<Checkbox />);
|
||||
[FormGroup, Input, Label].forEach(component => expect(checkBox.find(component)).toHaveLength(1));
|
||||
[FormGroup, Input, Label].forEach((component) => expect(checkBox.find(component)).toHaveLength(1));
|
||||
});
|
||||
|
||||
it('passes down the correct FormGroup props', () => {
|
||||
|
|
|
@ -23,11 +23,6 @@ describe('ToggleMoreLess', () => {
|
|||
|
||||
it('renders a show less btn if clicked', () => {
|
||||
tggleBtn.find(Button).simulate('click');
|
||||
expect(
|
||||
tggleBtn
|
||||
.find(Button)
|
||||
.render()
|
||||
.text()
|
||||
).toEqual('show less');
|
||||
expect(tggleBtn.find(Button).render().text()).toEqual('show less');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ export const StartingContent: FC<StartingContentProps> = ({ status, isUnexpected
|
|||
<div className="text-center m-3">
|
||||
<div className="m-4">
|
||||
<h2>Starting up...</h2>
|
||||
{status?.max! > 0 ? (
|
||||
{/* {status?.max! > 0 ? (
|
||||
<div>
|
||||
<p>
|
||||
Replaying WAL ({status?.current}/{status?.max})
|
||||
|
@ -37,19 +37,21 @@ export const StartingContent: FC<StartingContentProps> = ({ status, isUnexpected
|
|||
style={{ width: '10%', margin: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
) : null} */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const withStartingIndicator = <T extends {}>(Page: ComponentType<T>): FC<T> => ({ ...rest }) => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { ready, walReplayStatus, isUnexpected } = useFetchReadyInterval(pathPrefix);
|
||||
export const withStartingIndicator =
|
||||
<T extends Record<string, unknown>>(Page: ComponentType<T>): FC<T> =>
|
||||
({ ...rest }) => {
|
||||
const pathPrefix = usePathPrefix();
|
||||
const { ready, walReplayStatus, isUnexpected } = useFetchReadyInterval(pathPrefix);
|
||||
|
||||
if (ready || isUnexpected) {
|
||||
return <Page {...(rest as T)} />;
|
||||
}
|
||||
if (ready || isUnexpected) {
|
||||
return <Page {...(rest as T)} />;
|
||||
}
|
||||
|
||||
return <StartingContent isUnexpected={isUnexpected} status={walReplayStatus.data} />;
|
||||
};
|
||||
return <StartingContent isUnexpected={isUnexpected} status={walReplayStatus.data} />;
|
||||
};
|
||||
|
|
|
@ -10,37 +10,33 @@ interface StatusIndicatorProps {
|
|||
componentTitle?: string;
|
||||
}
|
||||
|
||||
export const withStatusIndicator = <T extends {}>(Component: ComponentType<T>): FC<StatusIndicatorProps & T> => ({
|
||||
error,
|
||||
isLoading,
|
||||
customErrorMsg,
|
||||
componentTitle,
|
||||
...rest
|
||||
}) => {
|
||||
if (error) {
|
||||
return (
|
||||
<Alert color="danger">
|
||||
{customErrorMsg ? (
|
||||
customErrorMsg
|
||||
) : (
|
||||
<>
|
||||
<strong>Error:</strong> Error fetching {componentTitle || Component.displayName}: {error.message}
|
||||
</>
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
export const withStatusIndicator =
|
||||
<T extends Record<string, any>>(Component: ComponentType<T>): FC<StatusIndicatorProps & T> =>
|
||||
({ error, isLoading, customErrorMsg, componentTitle, ...rest }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<Alert color="danger">
|
||||
{customErrorMsg ? (
|
||||
customErrorMsg
|
||||
) : (
|
||||
<>
|
||||
<strong>Error:</strong> Error fetching {componentTitle || Component.displayName}: {error.message}
|
||||
</>
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
size="3x"
|
||||
icon={faSpinner}
|
||||
spin
|
||||
className="position-absolute"
|
||||
style={{ transform: 'translate(-50%, -50%)', top: '50%', left: '50%' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <Component {...(rest as T)} />;
|
||||
};
|
||||
if (isLoading) {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
size="3x"
|
||||
icon={faSpinner}
|
||||
spin
|
||||
className="position-absolute"
|
||||
style={{ transform: 'translate(-50%, -50%)', top: '50%', left: '50%' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <Component {...(rest as T)} />;
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@ export interface FetchStateReadyInterval {
|
|||
walReplayStatus: WALReplayStatus;
|
||||
}
|
||||
|
||||
export const useFetch = <T extends {}>(url: string, options?: RequestInit): FetchState<T> => {
|
||||
export const useFetch = <T extends Record<string, any>>(url: string, options?: RequestInit): FetchState<T> => {
|
||||
const [response, setResponse] = useState<APIResponse<T>>({ status: 'start fetching' } as any);
|
||||
const [error, setError] = useState<Error>();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
@ -39,7 +39,7 @@ export const useFetch = <T extends {}>(url: string, options?: RequestInit): Fetc
|
|||
setResponse(json);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setError(error);
|
||||
setError(error as Error);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
|
|
17
web/ui/react-app/src/hooks/useMedia.ts
Normal file
17
web/ui/react-app/src/hooks/useMedia.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
|
||||
// A hook to determine whether a CSS media query finds any matches.
|
||||
const useMedia = (query: string) => {
|
||||
const mediaQuery = window.matchMedia(query);
|
||||
const [matches, setMatches] = useState(mediaQuery.matches);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => setMatches(mediaQuery.matches);
|
||||
mediaQuery.addEventListener('change', handler);
|
||||
return () => mediaQuery.removeEventListener('change', handler);
|
||||
}, []);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
export default useMedia;
|
|
@ -21,7 +21,7 @@ describe('AlertsContent', () => {
|
|||
{ selector: '#inactive-toggler', propName: 'inactive' },
|
||||
{ selector: '#pending-toggler', propName: 'pending' },
|
||||
{ selector: '#firing-toggler', propName: 'firing' },
|
||||
].forEach(testCase => {
|
||||
].forEach((testCase) => {
|
||||
it(`toggles the ${testCase.propName} checkbox from true to false when clicked and back to true when clicked again`, () => {
|
||||
expect(wrapper.find(testCase.selector).prop('checked')).toBe(true);
|
||||
wrapper.find(testCase.selector).simulate('change', { target: { checked: false } });
|
||||
|
|
|
@ -83,7 +83,7 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
|||
</Checkbox>
|
||||
</div>
|
||||
{groups.map((group, i) => {
|
||||
const hasFilterOn = group.rules.some(rule => filter[rule.state]);
|
||||
const hasFilterOn = group.rules.some((rule) => filter[rule.state]);
|
||||
return hasFilterOn ? (
|
||||
<Fragment key={i}>
|
||||
<GroupInfo rules={group.rules}>
|
||||
|
|
|
@ -18,7 +18,7 @@ const Alerts: FC = () => {
|
|||
};
|
||||
|
||||
if (response.data && response.data.groups) {
|
||||
response.data.groups.forEach(el => el.rules.forEach(r => ruleStatsCount[r.state]++));
|
||||
response.data.groups.forEach((el) => el.rules.forEach((r) => ruleStatsCount[r.state]++));
|
||||
}
|
||||
|
||||
return <AlertsWithStatusIndicator {...response.data} statsCount={ruleStatsCount} error={error} isLoading={isLoading} />;
|
||||
|
|
|
@ -67,11 +67,7 @@ describe('Flags', () => {
|
|||
|
||||
it('is sorted by flag by default', (): void => {
|
||||
const w = shallow(<FlagsContent data={sampleFlagsResponse} />);
|
||||
const td = w
|
||||
.find('tbody')
|
||||
.find('td')
|
||||
.find('span')
|
||||
.first();
|
||||
const td = w.find('tbody').find('td').find('span').first();
|
||||
expect(td.html()).toBe('<span>--alertmanager.notification-queue-capacity</span>');
|
||||
});
|
||||
|
||||
|
@ -82,11 +78,7 @@ describe('Flags', () => {
|
|||
.find('td')
|
||||
.filterWhere((td): boolean => td.hasClass('Flag'));
|
||||
th.simulate('click');
|
||||
const td = w
|
||||
.find('tbody')
|
||||
.find('td')
|
||||
.find('span')
|
||||
.first();
|
||||
const td = w.find('tbody').find('td').find('span').first();
|
||||
expect(td.html()).toBe('<span>--web.user-assets</span>');
|
||||
});
|
||||
|
||||
|
@ -97,7 +89,7 @@ describe('Flags', () => {
|
|||
const tds = w
|
||||
.find('tbody')
|
||||
.find('td')
|
||||
.filterWhere(code => code.hasClass('flag-item'));
|
||||
.filterWhere((code) => code.hasClass('flag-item'));
|
||||
expect(tds.length).toEqual(3);
|
||||
});
|
||||
|
||||
|
@ -108,7 +100,7 @@ describe('Flags', () => {
|
|||
const tds = w
|
||||
.find('tbody')
|
||||
.find('td')
|
||||
.filterWhere(code => code.hasClass('flag-value'));
|
||||
.filterWhere((code) => code.hasClass('flag-value'));
|
||||
expect(tds.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,15 +21,14 @@ interface FlagsProps {
|
|||
data?: FlagMap;
|
||||
}
|
||||
|
||||
const compareAlphaFn = (keys: boolean, reverse: boolean) => (
|
||||
[k1, v1]: [string, string],
|
||||
[k2, v2]: [string, string]
|
||||
): number => {
|
||||
const a = keys ? k1 : v1;
|
||||
const b = keys ? k2 : v2;
|
||||
const reverser = reverse ? -1 : 1;
|
||||
return reverser * a.localeCompare(b);
|
||||
};
|
||||
const compareAlphaFn =
|
||||
(keys: boolean, reverse: boolean) =>
|
||||
([k1, v1]: [string, string], [k2, v2]: [string, string]): number => {
|
||||
const a = keys ? k1 : v1;
|
||||
const b = keys ? k2 : v2;
|
||||
const reverser = reverse ? -1 : 1;
|
||||
return reverser * a.localeCompare(b);
|
||||
};
|
||||
|
||||
const getSortIcon = (b: boolean | undefined): IconDefinition => {
|
||||
if (b === undefined) {
|
||||
|
|
|
@ -33,14 +33,14 @@ describe('CMExpressionInput', () => {
|
|||
});
|
||||
|
||||
it('renders a search icon when it is not loading', () => {
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === 'prepend');
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === 'prepend');
|
||||
const icon = addon.find(FontAwesomeIcon);
|
||||
expect(icon.prop('icon')).toEqual(faSearch);
|
||||
});
|
||||
|
||||
it('renders a loading icon when it is loading', () => {
|
||||
const expressionInput = mount(<CMExpressionInput {...expressionInputProps} loading={true} />);
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === 'prepend');
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === 'prepend');
|
||||
const icon = addon.find(FontAwesomeIcon);
|
||||
expect(icon.prop('icon')).toEqual(faSpinner);
|
||||
expect(icon.prop('spin')).toBe(true);
|
||||
|
@ -52,11 +52,8 @@ describe('CMExpressionInput', () => {
|
|||
});
|
||||
|
||||
it('renders an execute button', () => {
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === 'append');
|
||||
const button = addon
|
||||
.find(Button)
|
||||
.find('.execute-btn')
|
||||
.first();
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === 'append');
|
||||
const button = addon.find(Button).find('.execute-btn').first();
|
||||
expect(button.prop('color')).toEqual('primary');
|
||||
expect(button.text()).toEqual('Execute');
|
||||
});
|
||||
|
@ -65,7 +62,7 @@ describe('CMExpressionInput', () => {
|
|||
const spyExecuteQuery = jest.fn();
|
||||
const props = { ...expressionInputProps, executeQuery: spyExecuteQuery };
|
||||
const wrapper = mount(<CMExpressionInput {...props} />);
|
||||
const btn = wrapper.find(Button).filterWhere(btn => btn.hasClass('execute-btn'));
|
||||
const btn = wrapper.find(Button).filterWhere((btn) => btn.hasClass('execute-btn'));
|
||||
btn.simulate('click');
|
||||
expect(spyExecuteQuery).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
@ -49,7 +49,7 @@ export class HistoryCompleteStrategy implements CompleteStrategy {
|
|||
}
|
||||
|
||||
promQL(context: CompletionContext): Promise<CompletionResult | null> | CompletionResult | null {
|
||||
return Promise.resolve(this.complete.promQL(context)).then(res => {
|
||||
return Promise.resolve(this.complete.promQL(context)).then((res) => {
|
||||
const { state, pos } = context;
|
||||
const tree = syntaxTree(state).resolve(pos, -1);
|
||||
const start = res != null ? res.from : tree.from;
|
||||
|
@ -61,7 +61,7 @@ export class HistoryCompleteStrategy implements CompleteStrategy {
|
|||
const historyItems: CompletionResult = {
|
||||
from: start,
|
||||
to: pos,
|
||||
options: this.queryHistory.map(q => ({
|
||||
options: this.queryHistory.map((q) => ({
|
||||
label: q.length < 80 ? q : q.slice(0, 76).concat('...'),
|
||||
detail: 'past query',
|
||||
apply: q,
|
||||
|
|
|
@ -69,12 +69,7 @@ describe('DataTable', () => {
|
|||
const table = dataTable.find(Table);
|
||||
table.find('tr').forEach((row, idx) => {
|
||||
expect(row.find(SeriesName)).toHaveLength(1);
|
||||
expect(
|
||||
row
|
||||
.find('td')
|
||||
.at(1)
|
||||
.text()
|
||||
).toEqual(`${idx}`);
|
||||
expect(row.find('td').at(1).text()).toEqual(`${idx}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -83,7 +78,7 @@ describe('DataTable', () => {
|
|||
const dataTableProps: QueryResult = {
|
||||
data: {
|
||||
resultType: 'vector',
|
||||
result: Array.from(Array(10001).keys()).map(i => {
|
||||
result: Array.from(Array(10001).keys()).map((i) => {
|
||||
return {
|
||||
metric: {
|
||||
__name__: `metric_name_${i}`,
|
||||
|
@ -104,12 +99,7 @@ describe('DataTable', () => {
|
|||
|
||||
it('renders a warning', () => {
|
||||
const alerts = dataTable.find(Alert);
|
||||
expect(
|
||||
alerts
|
||||
.first()
|
||||
.render()
|
||||
.text()
|
||||
).toEqual('Warning: Fetched 10001 metrics, only displaying first 10000.');
|
||||
expect(alerts.first().render().text()).toEqual('Warning: Fetched 10001 metrics, only displaying first 10000.');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -117,7 +107,7 @@ describe('DataTable', () => {
|
|||
const dataTableProps: QueryResult = {
|
||||
data: {
|
||||
resultType: 'vector',
|
||||
result: Array.from(Array(1001).keys()).map(i => {
|
||||
result: Array.from(Array(1001).keys()).map((i) => {
|
||||
return {
|
||||
metric: {
|
||||
__name__: `metric_name_${i}`,
|
||||
|
@ -133,12 +123,9 @@ describe('DataTable', () => {
|
|||
|
||||
it('renders a warning', () => {
|
||||
const alerts = dataTable.find(Alert);
|
||||
expect(
|
||||
alerts
|
||||
.first()
|
||||
.render()
|
||||
.text()
|
||||
).toEqual('Notice: Showing more than 1000 series, turning off label formatting for performance reasons.');
|
||||
expect(alerts.first().render().text()).toEqual(
|
||||
'Notice: Showing more than 1000 series, turning off label formatting for performance reasons.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -62,24 +62,22 @@ const DataTable: FC<QueryResult> = ({ data }) => {
|
|||
const doFormat = data.result.length <= maxFormattableSize;
|
||||
switch (data.resultType) {
|
||||
case 'vector':
|
||||
rows = (limitSeries(data.result) as InstantSample[]).map(
|
||||
(s: InstantSample, index: number): ReactNode => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<SeriesName labels={s.metric} format={doFormat} />
|
||||
</td>
|
||||
<td>{s.value[1]}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
);
|
||||
rows = (limitSeries(data.result) as InstantSample[]).map((s: InstantSample, index: number): ReactNode => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
<SeriesName labels={s.metric} format={doFormat} />
|
||||
</td>
|
||||
<td>{s.value[1]}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
limited = rows.length !== data.result.length;
|
||||
break;
|
||||
case 'matrix':
|
||||
rows = (limitSeries(data.result) as RangeSamples[]).map((s, index) => {
|
||||
const valueText = s.values
|
||||
.map(v => {
|
||||
.map((v) => {
|
||||
return v[1] + ' @' + v[0];
|
||||
})
|
||||
.join('\n');
|
||||
|
|
|
@ -47,14 +47,14 @@ describe('ExpressionInput', () => {
|
|||
});
|
||||
|
||||
it('renders a search icon when it is not loading', () => {
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === 'prepend');
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === 'prepend');
|
||||
const icon = addon.find(FontAwesomeIcon);
|
||||
expect(icon.prop('icon')).toEqual(faSearch);
|
||||
});
|
||||
|
||||
it('renders a loading icon when it is loading', () => {
|
||||
const expressionInput = mount(<ExpressionInput {...expressionInputProps} loading={true} />);
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === 'prepend');
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === 'prepend');
|
||||
const icon = addon.find(FontAwesomeIcon);
|
||||
expect(icon.prop('icon')).toEqual(faSpinner);
|
||||
expect(icon.prop('spin')).toBe(true);
|
||||
|
@ -75,7 +75,7 @@ describe('ExpressionInput', () => {
|
|||
const downshift = expressionInput.find(Downshift);
|
||||
const input = downshift.find(Input);
|
||||
downshift.setState({ isOpen: false });
|
||||
['Home', 'End', 'ArrowUp', 'ArrowDown'].forEach(key => {
|
||||
['Home', 'End', 'ArrowUp', 'ArrowDown'].forEach((key) => {
|
||||
const event = getKeyEvent(key);
|
||||
input.simulate('keydown', event);
|
||||
const nativeEvent = event.nativeEvent as any;
|
||||
|
@ -122,7 +122,7 @@ describe('ExpressionInput', () => {
|
|||
const spyExecuteQuery = jest.fn();
|
||||
const props = { ...expressionInputProps, executeQuery: spyExecuteQuery };
|
||||
const wrapper = mount(<ExpressionInput {...props} />);
|
||||
const btn = wrapper.find(Button).filterWhere(btn => btn.hasClass('execute-btn'));
|
||||
const btn = wrapper.find(Button).filterWhere((btn) => btn.hasClass('execute-btn'));
|
||||
btn.simulate('click');
|
||||
expect(spyExecuteQuery).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
@ -226,7 +226,7 @@ describe('ExpressionInput', () => {
|
|||
const downshift = expressionInput.find(Downshift);
|
||||
const input = downshift.find(Input);
|
||||
downshift.setState({ isOpen: true });
|
||||
['ArrowUp', 'ArrowDown'].forEach(key => {
|
||||
['ArrowUp', 'ArrowDown'].forEach((key) => {
|
||||
const event = getKeyEvent(key);
|
||||
input.simulate('keydown', event);
|
||||
const nativeEvent = event.nativeEvent as any;
|
||||
|
@ -248,7 +248,7 @@ describe('ExpressionInput', () => {
|
|||
const ul = downshift.find('ul');
|
||||
expect(ul.prop('className')).toEqual('card list-group');
|
||||
const items = ul.find('li');
|
||||
expect(items.map(item => item.text()).join(', ')).toEqual(
|
||||
expect(items.map((item) => item.text()).join(', ')).toEqual(
|
||||
'node_cpu_guest_seconds_total, node_cpu_seconds_total, instance:node_cpu_utilisation:rate1m'
|
||||
);
|
||||
});
|
||||
|
@ -256,11 +256,8 @@ describe('ExpressionInput', () => {
|
|||
});
|
||||
|
||||
it('renders an execute Button', () => {
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === 'append');
|
||||
const button = addon
|
||||
.find(Button)
|
||||
.find('.execute-btn')
|
||||
.first();
|
||||
const addon = expressionInput.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === 'append');
|
||||
const button = addon.find(Button).find('.execute-btn').first();
|
||||
expect(button.prop('color')).toEqual('primary');
|
||||
expect(button.text()).toEqual('Execute');
|
||||
});
|
||||
|
|
|
@ -167,7 +167,7 @@ class ExpressionInput extends Component<ExpressionInputProps, ExpressionInputSta
|
|||
return (
|
||||
<>
|
||||
<Downshift onSelect={this.setValue}>
|
||||
{downshift => (
|
||||
{(downshift) => (
|
||||
<div>
|
||||
<InputGroup className="expression-input">
|
||||
<InputGroupAddon addonType="prepend">
|
||||
|
|
|
@ -78,9 +78,9 @@ describe('Graph', () => {
|
|||
};
|
||||
it('renders a graph with props', () => {
|
||||
const graph = shallow(<Graph {...props} />);
|
||||
const div = graph.find('div').filterWhere(elem => elem.prop('className') === 'graph-test');
|
||||
const div = graph.find('div').filterWhere((elem) => elem.prop('className') === 'graph-test');
|
||||
const resize = div.find(ReactResizeDetector);
|
||||
const innerdiv = div.find('div').filterWhere(elem => elem.prop('className') === 'graph-chart');
|
||||
const innerdiv = div.find('div').filterWhere((elem) => elem.prop('className') === 'graph-chart');
|
||||
expect(resize.prop('handleWidth')).toBe(true);
|
||||
expect(div).toHaveLength(1);
|
||||
expect(innerdiv).toHaveLength(1);
|
||||
|
@ -264,10 +264,7 @@ describe('Graph', () => {
|
|||
);
|
||||
(graph.instance() as any).plot(); // create chart
|
||||
const spyPlotSetAndDraw = jest.spyOn(graph.instance() as any, 'plotSetAndDraw');
|
||||
graph
|
||||
.find('.legend-item')
|
||||
.at(0)
|
||||
.simulate('mouseover');
|
||||
graph.find('.legend-item').at(0).simulate('mouseover');
|
||||
expect(spyPlotSetAndDraw).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('should call spyPlotSetAndDraw with chartDate from state as default value', () => {
|
||||
|
|
|
@ -165,7 +165,7 @@ class Graph extends PureComponent<GraphProps, GraphState> {
|
|||
? [...chartData.series.map(toHoverColor(selectedIndex, this.props.stacked)), ...chartData.exemplars]
|
||||
: [
|
||||
...chartData.series.filter((_, i) => selected.includes(i)),
|
||||
...chartData.exemplars.filter(exemplar => {
|
||||
...chartData.exemplars.filter((exemplar) => {
|
||||
series: for (const i in selected) {
|
||||
for (const name in chartData.series[selected[i]].labels) {
|
||||
if (exemplar.seriesLabels[name] !== chartData.series[selected[i]].labels[name]) {
|
||||
|
|
|
@ -59,9 +59,9 @@ describe('GraphControls', () => {
|
|||
title: 'Increase range',
|
||||
icon: faPlus,
|
||||
},
|
||||
].forEach(testCase => {
|
||||
].forEach((testCase) => {
|
||||
const controls = shallow(<GraphControls {...defaultGraphControlProps} />);
|
||||
const addon = controls.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === testCase.position);
|
||||
const addon = controls.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === testCase.position);
|
||||
const button = addon.find(Button);
|
||||
const icon = button.find(FontAwesomeIcon);
|
||||
expect(button.prop('title')).toEqual(testCase.title);
|
||||
|
@ -109,7 +109,7 @@ describe('GraphControls', () => {
|
|||
|
||||
it('renders a resolution Input with props', () => {
|
||||
const controls = shallow(<GraphControls {...defaultGraphControlProps} />);
|
||||
const input = controls.find(Input).filterWhere(input => input.prop('className') === 'resolution-input');
|
||||
const input = controls.find(Input).filterWhere((input) => input.prop('className') === 'resolution-input');
|
||||
expect(input.prop('placeholder')).toEqual('Res. (s)');
|
||||
expect(input.prop('defaultValue')).toEqual('10');
|
||||
expect(input.prop('innerRef')).toEqual({ current: null });
|
||||
|
@ -140,10 +140,10 @@ describe('GraphControls', () => {
|
|||
icon: faChartArea,
|
||||
active: false,
|
||||
},
|
||||
].forEach(testCase => {
|
||||
].forEach((testCase) => {
|
||||
const controls = shallow(<GraphControls {...defaultGraphControlProps} />);
|
||||
const group = controls.find(ButtonGroup);
|
||||
const btn = group.find(Button).filterWhere(btn => btn.prop('title') === testCase.title);
|
||||
const btn = group.find(Button).filterWhere((btn) => btn.prop('title') === testCase.title);
|
||||
expect(btn.prop('active')).toEqual(testCase.active);
|
||||
const icon = btn.find(FontAwesomeIcon);
|
||||
expect(icon.prop('icon')).toEqual(testCase.icon);
|
||||
|
@ -160,14 +160,14 @@ describe('GraphControls', () => {
|
|||
title: 'Show stacked graph',
|
||||
active: false,
|
||||
},
|
||||
].forEach(testCase => {
|
||||
].forEach((testCase) => {
|
||||
const results: boolean[] = [];
|
||||
const onChange = (stacked: boolean): void => {
|
||||
results.push(stacked);
|
||||
};
|
||||
const controls = shallow(<GraphControls {...defaultGraphControlProps} onChangeStacking={onChange} />);
|
||||
const group = controls.find(ButtonGroup);
|
||||
const btn = group.find(Button).filterWhere(btn => btn.prop('title') === testCase.title);
|
||||
const btn = group.find(Button).filterWhere((btn) => btn.prop('title') === testCase.title);
|
||||
const onClick = btn.prop('onClick');
|
||||
if (onClick) {
|
||||
onClick({} as React.MouseEvent);
|
||||
|
|
|
@ -46,7 +46,7 @@ class GraphControls extends Component<GraphControlsProps> {
|
|||
182 * 24 * 60 * 60,
|
||||
365 * 24 * 60 * 60,
|
||||
730 * 24 * 60 * 60,
|
||||
].map(s => s * 1000);
|
||||
].map((s) => s * 1000);
|
||||
|
||||
onChangeRangeInput = (rangeText: string): void => {
|
||||
const range = parseDuration(rangeText);
|
||||
|
@ -92,7 +92,7 @@ class GraphControls extends Component<GraphControlsProps> {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<Form inline className="graph-controls" onSubmit={e => e.preventDefault()}>
|
||||
<Form inline className="graph-controls" onSubmit={(e) => e.preventDefault()}>
|
||||
<InputGroup className="range-input" size="sm">
|
||||
<InputGroupAddon addonType="prepend">
|
||||
<Button title="Decrease range" onClick={this.decreaseRange}>
|
||||
|
|
|
@ -60,7 +60,7 @@ describe('GraphHelpers', () => {
|
|||
{ input: 2e-24, output: '2.00y' },
|
||||
{ input: 2e-25, output: '0.20y' },
|
||||
{ input: 2e-26, output: '0.02y' },
|
||||
].map(t => {
|
||||
].map((t) => {
|
||||
expect(formatValue(t.input)).toBe(t.output);
|
||||
});
|
||||
});
|
||||
|
@ -80,7 +80,7 @@ describe('GraphHelpers', () => {
|
|||
};
|
||||
expect(
|
||||
getColors(data)
|
||||
.map(c => c.toString())
|
||||
.map((c) => c.toString())
|
||||
.join(',')
|
||||
).toEqual(
|
||||
'rgb(237,194,64),rgb(175,216,248),rgb(203,75,75),rgb(77,167,77),rgb(148,64,237),rgb(189,155,51),rgb(140,172,198)'
|
||||
|
|
|
@ -113,8 +113,8 @@ export const getOptions = (stacked: boolean, useLocalTime: boolean): jquery.flot
|
|||
${Object.keys(labels).length === 0 ? '<div class="mb-1 font-italic">no labels</div>' : ''}
|
||||
${labels['__name__'] ? `<div class="mb-1"><strong>${labels['__name__']}</strong></div>` : ''}
|
||||
${Object.keys(labels)
|
||||
.filter(k => k !== '__name__')
|
||||
.map(k => `<div class="mb-1"><strong>${k}</strong>: ${escapeHTML(labels[k])}</div>`)
|
||||
.filter((k) => k !== '__name__')
|
||||
.map((k) => `<div class="mb-1"><strong>${k}</strong>: ${escapeHTML(labels[k])}</div>`)
|
||||
.join('')}
|
||||
</div>`;
|
||||
|
||||
|
@ -233,7 +233,7 @@ export const normalizeData = ({ queryParams, data, exemplars, stacked }: GraphPr
|
|||
index,
|
||||
};
|
||||
}),
|
||||
exemplars: Object.values(buckets).flatMap(bucket => {
|
||||
exemplars: Object.values(buckets).flatMap((bucket) => {
|
||||
if (bucket.length === 1) {
|
||||
return bucket[0];
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ const exemplarSymbol = (ctx: CanvasRenderingContext2D, x: number, y: number) =>
|
|||
const stdDeviation = (sum: number, values: number[]): number => {
|
||||
const avg = sum / values.length;
|
||||
let squaredAvg = 0;
|
||||
values.map(value => (squaredAvg += (value - avg) ** 2));
|
||||
values.map((value) => (squaredAvg += (value - avg) ** 2));
|
||||
squaredAvg = squaredAvg / values.length;
|
||||
return Math.sqrt(squaredAvg);
|
||||
};
|
||||
|
|
|
@ -31,7 +31,7 @@ export class Legend extends PureComponent<LegendProps, LegendState> {
|
|||
if (ev.ctrlKey || ev.metaKey) {
|
||||
const { chartData } = this.props;
|
||||
if (selectedIndexes.includes(index)) {
|
||||
selected = selectedIndexes.filter(idx => idx !== index);
|
||||
selected = selectedIndexes.filter((idx) => idx !== index);
|
||||
} else {
|
||||
selected =
|
||||
// Flip the logic - In case none is selected ctrl + click should deselect clicked series.
|
||||
|
|
|
@ -8,7 +8,7 @@ interface Props {
|
|||
insertAtCursor(value: string): void;
|
||||
}
|
||||
|
||||
class MetricsExplorer extends Component<Props, {}> {
|
||||
class MetricsExplorer extends Component<Props> {
|
||||
handleMetricClick = (query: string) => {
|
||||
this.props.insertAtCursor(query);
|
||||
this.props.updateShow(false);
|
||||
|
@ -23,7 +23,7 @@ class MetricsExplorer extends Component<Props, {}> {
|
|||
<Modal isOpen={this.props.show} toggle={this.toggle} className="metrics-explorer">
|
||||
<ModalHeader toggle={this.toggle}>Metrics Explorer</ModalHeader>
|
||||
<ModalBody>
|
||||
{this.props.metrics.map(metric => (
|
||||
{this.props.metrics.map((metric) => (
|
||||
<p key={metric} className="metric" onClick={this.handleMetricClick.bind(this, metric)}>
|
||||
{metric}
|
||||
</p>
|
||||
|
|
|
@ -75,7 +75,7 @@ describe('Panel', () => {
|
|||
});
|
||||
|
||||
it('renders a TabPane with a TimeInput and a DataTable when in table mode', () => {
|
||||
const tab = panel.find(TabPane).filterWhere(tab => tab.prop('tabId') === 'table');
|
||||
const tab = panel.find(TabPane).filterWhere((tab) => tab.prop('tabId') === 'table');
|
||||
const timeInput = tab.find(TimeInput);
|
||||
expect(timeInput.prop('time')).toEqual(defaultProps.options.endTime);
|
||||
expect(timeInput.prop('range')).toEqual(defaultProps.options.range);
|
||||
|
|
|
@ -151,7 +151,7 @@ class Panel extends Component<PanelProps, PanelState> {
|
|||
cache: 'no-store',
|
||||
credentials: 'same-origin',
|
||||
signal: abortController.signal,
|
||||
}).then(resp => resp.json());
|
||||
}).then((resp) => resp.json());
|
||||
|
||||
if (query.status !== 'success') {
|
||||
throw new Error(query.error || 'invalid response JSON');
|
||||
|
@ -163,7 +163,7 @@ class Panel extends Component<PanelProps, PanelState> {
|
|||
cache: 'no-store',
|
||||
credentials: 'same-origin',
|
||||
signal: abortController.signal,
|
||||
}).then(resp => resp.json());
|
||||
}).then((resp) => resp.json());
|
||||
|
||||
if (exemplars.status !== 'success') {
|
||||
throw new Error(exemplars.error || 'invalid response JSON');
|
||||
|
@ -198,7 +198,7 @@ class Panel extends Component<PanelProps, PanelState> {
|
|||
loading: false,
|
||||
});
|
||||
this.abortInFlightFetch = null;
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (error.name === 'AbortError') {
|
||||
// Aborts are expected, don't show an error for them.
|
||||
return;
|
||||
|
@ -210,7 +210,7 @@ class Panel extends Component<PanelProps, PanelState> {
|
|||
}
|
||||
};
|
||||
|
||||
setOptions(opts: object): void {
|
||||
setOptions(opts: Partial<PanelOptions>): void {
|
||||
const newOpts = { ...this.props.options, ...opts };
|
||||
this.props.onOptionsChanged(newOpts);
|
||||
}
|
||||
|
|
|
@ -91,8 +91,8 @@ export const PanelListContent: FC<PanelListContentProps> = ({
|
|||
key={id}
|
||||
id={id}
|
||||
options={options}
|
||||
onOptionsChanged={opts =>
|
||||
callAll(setPanels, updateURL)(panels.map(p => (id === p.id ? { ...p, options: opts } : p)))
|
||||
onOptionsChanged={(opts) =>
|
||||
callAll(setPanels, updateURL)(panels.map((p) => (id === p.id ? { ...p, options: opts } : p)))
|
||||
}
|
||||
removePanel={() =>
|
||||
callAll(
|
||||
|
|
|
@ -6,7 +6,7 @@ export interface QueryStats {
|
|||
resultSeries: number;
|
||||
}
|
||||
|
||||
const QueryStatsView: FC<QueryStats> = props => {
|
||||
const QueryStatsView: FC<QueryStats> = (props) => {
|
||||
const { loadTime, resolution, resultSeries } = props;
|
||||
|
||||
return (
|
||||
|
|
|
@ -55,7 +55,7 @@ describe('SeriesName', () => {
|
|||
const child = seriesName.childAt(i);
|
||||
const text = child
|
||||
.children()
|
||||
.map(ch => ch.text())
|
||||
.map((ch) => ch.text())
|
||||
.join('');
|
||||
switch (child.children().length) {
|
||||
case 1:
|
||||
|
|
|
@ -39,11 +39,11 @@ describe('TimeInput', () => {
|
|||
title: 'Increase time',
|
||||
icon: faChevronRight,
|
||||
},
|
||||
].forEach(button => {
|
||||
].forEach((button) => {
|
||||
const onChangeTime = sinon.spy();
|
||||
const timeInput = shallow(<TimeInput {...timeInputProps} onChangeTime={onChangeTime} />);
|
||||
const addon = timeInput.find(InputGroupAddon).filterWhere(addon => addon.prop('addonType') === button.position);
|
||||
const btn = addon.find(Button).filterWhere(btn => btn.prop('title') === button.title);
|
||||
const addon = timeInput.find(InputGroupAddon).filterWhere((addon) => addon.prop('addonType') === button.position);
|
||||
const btn = addon.find(Button).filterWhere((btn) => btn.prop('title') === button.title);
|
||||
const icon = btn.find(FontAwesomeIcon);
|
||||
expect(icon.prop('icon')).toEqual(button.icon);
|
||||
expect(icon.prop('fixedWidth')).toBe(true);
|
||||
|
|
|
@ -81,7 +81,7 @@ class TimeInput extends Component<TimeInputProps> {
|
|||
this.$time.on('change.datetimepicker', (e: any) => {
|
||||
// The end time can also be set by dragging a section on the graph,
|
||||
// and that value will have decimal places.
|
||||
if (e.date && e.date.valueOf() !== Math.trunc(this.props.time?.valueOf()!)) {
|
||||
if (e.date && e.date.valueOf() !== Math.trunc(this.props.time?.valueOf() || NaN)) {
|
||||
this.props.onChangeTime(e.date.valueOf());
|
||||
}
|
||||
});
|
||||
|
@ -115,7 +115,7 @@ class TimeInput extends Component<TimeInputProps> {
|
|||
innerRef={this.timeInputRef}
|
||||
onFocus={() => this.$time.datetimepicker('show')}
|
||||
onBlur={() => this.$time.datetimepicker('hide')}
|
||||
onKeyDown={e => ['Escape', 'Enter'].includes(e.key) && this.$time.datetimepicker('hide')}
|
||||
onKeyDown={(e) => ['Escape', 'Enter'].includes(e.key) && this.$time.datetimepicker('hide')}
|
||||
/>
|
||||
|
||||
{/* CAUTION: While the datetimepicker also has an option to show a 'clear' button,
|
||||
|
|
|
@ -22,7 +22,7 @@ export interface RulesMap {
|
|||
groups: RuleGroup[];
|
||||
}
|
||||
|
||||
const GraphExpressionLink: FC<{ expr: string; text: string; title: string }> = props => {
|
||||
const GraphExpressionLink: FC<{ expr: string; text: string; title: string }> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<strong>{props.title}:</strong>
|
||||
|
|
|
@ -24,9 +24,9 @@ describe('EndpointLink', () => {
|
|||
expect(anchor.children().text()).toEqual('http://100.99.128.71:9115/probe');
|
||||
expect(endpointLink.find('br')).toHaveLength(1);
|
||||
expect(badges).toHaveLength(2);
|
||||
const moduleLabel = badges.filterWhere(badge => badge.children().text() === 'module="http_2xx"');
|
||||
const moduleLabel = badges.filterWhere((badge) => badge.children().text() === 'module="http_2xx"');
|
||||
expect(moduleLabel.length).toEqual(1);
|
||||
const targetLabel = badges.filterWhere(badge => badge.children().text() === 'target="http://some-service"');
|
||||
const targetLabel = badges.filterWhere((badge) => badge.children().text() === 'target="http://some-service"');
|
||||
expect(targetLabel.length).toEqual(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ const EndpointLink: FC<EndpointLinkProps> = ({ endpoint, globalUrl }) => {
|
|||
let url: URL;
|
||||
try {
|
||||
url = new URL(endpoint);
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
return (
|
||||
<Alert color="danger">
|
||||
<strong>Error:</strong> {e.message}
|
||||
|
|
|
@ -15,7 +15,7 @@ describe('Filter', () => {
|
|||
showUnhealthy: true,
|
||||
};
|
||||
let setFilter: SinonSpy;
|
||||
let filterWrapper: ShallowWrapper<FilterProps, Readonly<{}>, Component<{}, {}, Component>>;
|
||||
let filterWrapper: ShallowWrapper<FilterProps, Readonly<unknown>, Component<unknown, unknown, Component>>;
|
||||
beforeEach(() => {
|
||||
setFilter = sinon.spy();
|
||||
setExpaned = sinon.spy();
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('ScrapePoolList', () => {
|
|||
expect(panels).toHaveLength(3);
|
||||
const activeTargets: Target[] = sampleApiResponse.data.activeTargets as Target[];
|
||||
activeTargets.forEach(({ scrapePool }: Target) => {
|
||||
const panel = scrapePoolList.find(ScrapePoolPanel).filterWhere(panel => panel.prop('scrapePool') === scrapePool);
|
||||
const panel = scrapePoolList.find(ScrapePoolPanel).filterWhere((panel) => panel.prop('scrapePool') === scrapePool);
|
||||
expect(panel).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,12 +34,12 @@ export const ScrapePoolContent: FC<ScrapePoolListProps> = ({ activeTargets }) =>
|
|||
<>
|
||||
<Filter filter={filter} setFilter={setFilter} expanded={expanded} setExpanded={setExpanded} />
|
||||
{Object.keys(targetGroups)
|
||||
.filter(scrapePool => {
|
||||
.filter((scrapePool) => {
|
||||
const targetGroup = targetGroups[scrapePool];
|
||||
const isHealthy = targetGroup.upCount === targetGroup.targets.length;
|
||||
return (isHealthy && showHealthy) || (!isHealthy && showUnhealthy);
|
||||
})
|
||||
.map<JSX.Element>(scrapePool => (
|
||||
.map<JSX.Element>((scrapePool) => (
|
||||
<ScrapePoolPanel
|
||||
key={scrapePool}
|
||||
scrapePool={scrapePool}
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('ScrapePoolPanel', () => {
|
|||
const scrapePoolPanel = shallow(<ScrapePoolPanel {...defaultProps} />);
|
||||
|
||||
it('renders a container', () => {
|
||||
const div = scrapePoolPanel.find('div').filterWhere(elem => elem.hasClass('container'));
|
||||
const div = scrapePoolPanel.find('div').filterWhere((elem) => elem.hasClass('container'));
|
||||
expect(div).toHaveLength(1);
|
||||
});
|
||||
|
||||
|
@ -76,7 +76,7 @@ describe('ScrapePoolPanel', () => {
|
|||
const headers = table.find('th');
|
||||
expect(table).toHaveLength(1);
|
||||
expect(headers).toHaveLength(6);
|
||||
columns.forEach(col => {
|
||||
columns.forEach((col) => {
|
||||
expect(headers.contains(col));
|
||||
});
|
||||
});
|
||||
|
@ -94,7 +94,7 @@ describe('ScrapePoolPanel', () => {
|
|||
});
|
||||
|
||||
it('renders a badge for health', () => {
|
||||
const td = row.find('td').filterWhere(elem => Boolean(elem.hasClass('state')));
|
||||
const td = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('state')));
|
||||
const badge = td.find(Badge);
|
||||
expect(badge).toHaveLength(1);
|
||||
expect(badge.prop('color')).toEqual(getColor(health));
|
||||
|
@ -109,17 +109,17 @@ describe('ScrapePoolPanel', () => {
|
|||
});
|
||||
|
||||
it('renders last scrape time', () => {
|
||||
const lastScrapeCell = row.find('td').filterWhere(elem => Boolean(elem.hasClass('last-scrape')));
|
||||
const lastScrapeCell = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('last-scrape')));
|
||||
expect(lastScrapeCell).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders last scrape duration', () => {
|
||||
const lastScrapeCell = row.find('td').filterWhere(elem => Boolean(elem.hasClass('scrape-duration')));
|
||||
const lastScrapeCell = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('scrape-duration')));
|
||||
expect(lastScrapeCell).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a badge for Errors', () => {
|
||||
const td = row.find('td').filterWhere(elem => Boolean(elem.hasClass('errors')));
|
||||
const td = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('errors')));
|
||||
const badge = td.find(Badge);
|
||||
expect(badge).toHaveLength(lastError ? 1 : 0);
|
||||
if (lastError) {
|
||||
|
|
|
@ -37,7 +37,7 @@ const ScrapePoolPanel: FC<PanelProps> = ({ scrapePool, targetGroup, expanded, to
|
|||
<Table className={styles.table} size="sm" bordered hover striped>
|
||||
<thead>
|
||||
<tr key="header">
|
||||
{columns.map(column => (
|
||||
{columns.map((column) => (
|
||||
<th key={column}>{column}</th>
|
||||
))}
|
||||
</tr>
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('targetLabels', () => {
|
|||
const targetLabels = shallow(<TargetLabels {...defaultProps} />);
|
||||
|
||||
it('renders a div of series labels', () => {
|
||||
const div = targetLabels.find('div').filterWhere(elem => elem.hasClass('series-labels-container'));
|
||||
const div = targetLabels.find('div').filterWhere((elem) => elem.hasClass('series-labels-container'));
|
||||
expect(div).toHaveLength(1);
|
||||
expect(div.prop('id')).toEqual('series-labels-cortex/node-exporter_group/0-1');
|
||||
});
|
||||
|
@ -33,7 +33,7 @@ describe('targetLabels', () => {
|
|||
Object.keys(l).forEach((labelName: string): void => {
|
||||
const badge = targetLabels
|
||||
.find(Badge)
|
||||
.filterWhere(badge => badge.children().text() === `${labelName}="${l[labelName]}"`);
|
||||
.filterWhere((badge) => badge.children().text() === `${labelName}="${l[labelName]}"`);
|
||||
expect(badge).toHaveLength(1);
|
||||
});
|
||||
expect(targetLabels.find(Badge)).toHaveLength(3);
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface TargetLabelsProps {
|
|||
scrapePool: string;
|
||||
}
|
||||
|
||||
const formatLabels = (labels: Labels): string[] => Object.keys(labels).map(key => `${key}="${labels[key]}"`);
|
||||
const formatLabels = (labels: Labels): string[] => Object.keys(labels).map((key) => `${key}="${labels[key]}"`);
|
||||
|
||||
const TargetLabels: FC<TargetLabelsProps> = ({ discoveredLabels, labels, idx, scrapePool }) => {
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false);
|
||||
|
@ -25,7 +25,7 @@ const TargetLabels: FC<TargetLabelsProps> = ({ discoveredLabels, labels, idx, sc
|
|||
return (
|
||||
<>
|
||||
<div id={id} className="series-labels-container">
|
||||
{Object.keys(labels).map(labelName => {
|
||||
{Object.keys(labels).map((labelName) => {
|
||||
return (
|
||||
<Badge color="primary" className="mr-1" key={labelName}>
|
||||
{`${labelName}="${labels[labelName]}"`}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint @typescript-eslint/camelcase: 0 */
|
||||
|
||||
import { ScrapePools } from '../target';
|
||||
|
||||
export const targetGroups: ScrapePools = Object.freeze({
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/* eslint @typescript-eslint/camelcase: 0 */
|
||||
|
||||
import { sampleApiResponse } from './__testdata__/testdata';
|
||||
import { groupTargets, Target, ScrapePools, getColor } from './target';
|
||||
|
||||
|
@ -8,7 +6,7 @@ describe('groupTargets', () => {
|
|||
const targetGroups: ScrapePools = groupTargets(targets);
|
||||
|
||||
it('groups a list of targets by scrape job', () => {
|
||||
['blackbox', 'prometheus/test', 'node_exporter'].forEach(scrapePool => {
|
||||
['blackbox', 'prometheus/test', 'node_exporter'].forEach((scrapePool) => {
|
||||
expect(Object.keys(targetGroups)).toContain(scrapePool);
|
||||
});
|
||||
Object.keys(targetGroups).forEach((scrapePool: string): void => {
|
||||
|
|
|
@ -121,11 +121,7 @@ describe('TSDB Stats', () => {
|
|||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
const headStats = page
|
||||
.find(Table)
|
||||
.at(0)
|
||||
.find('tbody')
|
||||
.find('td');
|
||||
const headStats = page.find(Table).at(0).find('tbody').find('td');
|
||||
['508', '937', '1234', '2020-06-07T08:00:00.000Z (1591516800000)', '2020-08-31T18:00:00.143Z (1598896800143)'].forEach(
|
||||
(value, i) => {
|
||||
expect(headStats.at(i).text()).toEqual(value);
|
||||
|
@ -170,11 +166,7 @@ describe('TSDB Stats', () => {
|
|||
|
||||
expect(page.find('h2').text()).toEqual('TSDB Status');
|
||||
|
||||
const headStats = page
|
||||
.find(Table)
|
||||
.at(0)
|
||||
.find('tbody')
|
||||
.find('td');
|
||||
const headStats = page.find(Table).at(0).find('tbody').find('td');
|
||||
['0', '0', '0', 'No datapoints yet', 'No datapoints yet'].forEach((value, i) => {
|
||||
expect(headStats.at(i).text()).toEqual(value);
|
||||
});
|
||||
|
@ -199,11 +191,7 @@ describe('TSDB Stats', () => {
|
|||
|
||||
expect(page.find('h2').text()).toEqual('TSDB Status');
|
||||
|
||||
const headStats = page
|
||||
.find(Table)
|
||||
.at(0)
|
||||
.find('tbody')
|
||||
.find('td');
|
||||
const headStats = page.find(Table).at(0).find('tbody').find('td');
|
||||
['1', '0', '0', 'Error parsing time (9223372036854776000)', 'Error parsing time (-9223372036854776000)'].forEach(
|
||||
(value, i) => {
|
||||
expect(headStats.at(i).text()).toEqual(value);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { configure } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
|
||||
import { GlobalWithFetchMock } from 'jest-fetch-mock';
|
||||
import 'mutationobserver-shim'; // Needed for CodeMirror.
|
||||
import './globals';
|
||||
|
@ -11,12 +11,12 @@ customGlobal.fetchMock = customGlobal.fetch;
|
|||
|
||||
// CodeMirror in the expression input requires this DOM API. When we upgrade react-scripts
|
||||
// and the associated Jest deps, hopefully this won't be needed anymore.
|
||||
document.getSelection = function() {
|
||||
document.getSelection = function () {
|
||||
return {
|
||||
addRange: function() {
|
||||
addRange: function () {
|
||||
return;
|
||||
},
|
||||
removeAllRanges: function() {
|
||||
removeAllRanges: function () {
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
|
2
web/ui/react-app/src/types/index.d.ts
vendored
2
web/ui/react-app/src/types/index.d.ts
vendored
|
@ -1,9 +1,7 @@
|
|||
declare namespace jquery.flot {
|
||||
// eslint-disable-next-line @typescript-eslint/class-name-casing
|
||||
interface plot extends jquery.flot.plot {
|
||||
destroy: () => void;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/class-name-casing
|
||||
interface plotOptions extends jquery.flot.plotOptions {
|
||||
tooltip: {
|
||||
show?: boolean;
|
||||
|
|
|
@ -4,9 +4,7 @@ import { PanelOptions, PanelType, PanelDefaultOptions } from '../pages/graph/Pan
|
|||
import { PanelMeta } from '../pages/graph/PanelList';
|
||||
|
||||
export const generateID = () => {
|
||||
return `_${Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9)}`;
|
||||
return `_${Math.random().toString(36).substr(2, 9)}`;
|
||||
};
|
||||
|
||||
export const byEmptyString = (p: string) => p.length > 0;
|
||||
|
@ -23,7 +21,7 @@ export const escapeHTML = (str: string): string => {
|
|||
'/': '/',
|
||||
};
|
||||
|
||||
return String(str).replace(/[&<>"'/]/g, function(s) {
|
||||
return String(str).replace(/[&<>"'/]/g, function (s) {
|
||||
return entityMap[s];
|
||||
});
|
||||
};
|
||||
|
@ -251,10 +249,12 @@ export const mapObjEntries = <T, key extends keyof T, Z>(
|
|||
cb: ([k, v]: [string, T[key]], i: number, arr: [string, T[key]][]) => Z
|
||||
) => Object.entries(o).map(cb);
|
||||
|
||||
export const callAll = (...fns: Array<(...args: any) => void>) => (...args: any) => {
|
||||
// eslint-disable-next-line prefer-spread
|
||||
fns.filter(Boolean).forEach(fn => fn.apply(null, args));
|
||||
};
|
||||
export const callAll =
|
||||
(...fns: Array<(...args: any) => void>) =>
|
||||
(...args: any) => {
|
||||
// eslint-disable-next-line prefer-spread
|
||||
fns.filter(Boolean).forEach((fn) => fn.apply(null, args));
|
||||
};
|
||||
|
||||
export const parsePrometheusFloat = (value: string) => {
|
||||
if (isNaN(Number(value))) {
|
||||
|
|
|
@ -136,7 +136,7 @@ describe('Utils', () => {
|
|||
},
|
||||
];
|
||||
|
||||
tests.forEach(t => {
|
||||
tests.forEach((t) => {
|
||||
it(t.input, () => {
|
||||
const d = parseDuration(t.input);
|
||||
expect(d).toEqual(t.output);
|
||||
|
@ -148,7 +148,7 @@ describe('Utils', () => {
|
|||
describe('should fail to parse invalid durations', () => {
|
||||
const tests = ['1', '1y1m1d', '-1w', '1.5d', 'd', ''];
|
||||
|
||||
tests.forEach(t => {
|
||||
tests.forEach((t) => {
|
||||
it(t, () => {
|
||||
expect(parseDuration(t)).toBe(null);
|
||||
});
|
||||
|
|
|
@ -17,9 +17,12 @@
|
|||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve"
|
||||
"jsx": "react-jsx",
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src", "test", "react-app-env.d.ts"
|
||||
"src",
|
||||
"test",
|
||||
"react-app-env.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue