mirror of
https://github.com/getredash/redash.git
synced 2025-12-19 17:37:19 -05:00
Change eslint configuration and fix resulting issues (#4423)
* Remove app/service/query-string (unused) and its dependency. * Fix usage of mixed operators. * eslint --fix fixes for missing dependencies for react hooks * Fix: useCallback dependency passed to $http's .catch. * Satisfy react/no-direct-mutation-state. * Fix no-mixed-operators violations. * Move the decision of whether to render Custom chart one level up to make sure hooks are called in the same order. * Fix: name was undefined. It wasn't detected before because there is such global. * Simplify eslint config and switch to creat-react-app's eslint base. * Add prettier config. * Make sure eslint doesn't conflict with prettier * A few updates post eslint (#4425) * Prettier command in package.json
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
build/*.js
|
||||
dist
|
||||
config/*.js
|
||||
client/dist
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["airbnb", "plugin:compat/recommended"],
|
||||
extends: ["react-app", "plugin:compat/recommended", "prettier"],
|
||||
plugins: ["jest", "compat", "no-only-tests"],
|
||||
settings: {
|
||||
"import/resolver": "webpack"
|
||||
},
|
||||
parser: "babel-eslint",
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
@@ -13,54 +12,6 @@ module.exports = {
|
||||
rules: {
|
||||
// allow debugger during development
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? 2 : 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-mixed-operators": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-use-before-define": ["error", "nofunc"],
|
||||
"prefer-destructuring": "off",
|
||||
"prefer-template": "off",
|
||||
"no-restricted-properties": "off",
|
||||
"no-restricted-globals": "off",
|
||||
"no-multi-assign": "off",
|
||||
"no-lonely-if": "off",
|
||||
"consistent-return": "off",
|
||||
"no-control-regex": "off",
|
||||
"no-multiple-empty-lines": "warn",
|
||||
"no-only-tests/no-only-tests": "error",
|
||||
"operator-linebreak": "off",
|
||||
"react/destructuring-assignment": "off",
|
||||
"react/jsx-filename-extension": "off",
|
||||
"react/jsx-one-expression-per-line": "off",
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react/jsx-wrap-multilines": "warn",
|
||||
"react/no-access-state-in-setstate": "warn",
|
||||
"react/prefer-stateless-function": "warn",
|
||||
"react/forbid-prop-types": "warn",
|
||||
"react/prop-types": "warn",
|
||||
"jsx-a11y/anchor-is-valid": "off",
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/label-has-associated-control": [
|
||||
"warn",
|
||||
{
|
||||
controlComponents: true
|
||||
}
|
||||
],
|
||||
"jsx-a11y/label-has-for": "off",
|
||||
"jsx-a11y/no-static-element-interactions": "off",
|
||||
"max-len": [
|
||||
"error",
|
||||
120,
|
||||
2,
|
||||
{
|
||||
ignoreUrls: true,
|
||||
ignoreComments: false,
|
||||
ignoreRegExpLiterals: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true
|
||||
}
|
||||
],
|
||||
"no-else-return": ["error", { allowElseIf: true }],
|
||||
"object-curly-newline": ["error", { consistent: true }]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,13 +86,13 @@ function EditParameterSettingsDialog(props) {
|
||||
|
||||
// fetch query by id
|
||||
useEffect(() => {
|
||||
const { queryId } = props.parameter;
|
||||
const queryId = props.parameter.queryId;
|
||||
if (queryId) {
|
||||
Query.get({ id: queryId }, (query) => {
|
||||
setInitialQuery(query);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [props.parameter.queryId]);
|
||||
|
||||
function isFulfilled() {
|
||||
// name
|
||||
|
||||
@@ -2,22 +2,15 @@ import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { debounce, find } from 'lodash';
|
||||
import { find } from 'lodash';
|
||||
import Input from 'antd/lib/input';
|
||||
import Select from 'antd/lib/select';
|
||||
import { Query } from '@/services/query';
|
||||
import notification from '@/services/notification';
|
||||
import { QueryTagsControl } from '@/components/tags-control/TagsControl';
|
||||
import useSearchResults from '@/lib/hooks/useSearchResults';
|
||||
|
||||
const SEARCH_DEBOUNCE_DURATION = 200;
|
||||
const { Option } = Select;
|
||||
|
||||
class StaleSearchError extends Error {
|
||||
constructor() {
|
||||
super('stale search');
|
||||
}
|
||||
}
|
||||
|
||||
function search(term) {
|
||||
// get recent
|
||||
if (!term) {
|
||||
@@ -34,17 +27,16 @@ function search(term) {
|
||||
}
|
||||
|
||||
export function QuerySelector(props) {
|
||||
const [searchTerm, setSearchTerm] = useState();
|
||||
const [searching, setSearching] = useState();
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedQuery, setSelectedQuery] = useState();
|
||||
const [doSearch, searchResults, searching] = useSearchResults(search, { initialResults: [] });
|
||||
|
||||
let isStaleSearch = false;
|
||||
const debouncedSearch = debounce(_search, SEARCH_DEBOUNCE_DURATION);
|
||||
const placeholder = 'Search a query by name';
|
||||
const clearIcon = <i className="fa fa-times hide-in-percy" onClick={() => selectQuery(null)} />;
|
||||
const spinIcon = <i className={cx('fa fa-spinner fa-pulse hide-in-percy', { hidden: !searching })} />;
|
||||
|
||||
useEffect(() => { doSearch(searchTerm); }, [doSearch, searchTerm]);
|
||||
|
||||
// set selected from prop
|
||||
useEffect(() => {
|
||||
if (props.selectedQuery) {
|
||||
@@ -52,43 +44,6 @@ export function QuerySelector(props) {
|
||||
}
|
||||
}, [props.selectedQuery]);
|
||||
|
||||
// on search term changed, debounced
|
||||
useEffect(() => {
|
||||
// clear results, no search
|
||||
if (searchTerm === null) {
|
||||
setSearchResults(null);
|
||||
return () => {};
|
||||
}
|
||||
|
||||
// search
|
||||
debouncedSearch(searchTerm);
|
||||
return () => {
|
||||
debouncedSearch.cancel();
|
||||
isStaleSearch = true;
|
||||
};
|
||||
}, [searchTerm]);
|
||||
|
||||
function _search(term) {
|
||||
setSearching(true);
|
||||
search(term)
|
||||
.then(rejectStale)
|
||||
.then((results) => {
|
||||
setSearchResults(results);
|
||||
setSearching(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!(err instanceof StaleSearchError)) {
|
||||
setSearching(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function rejectStale(results) {
|
||||
return isStaleSearch
|
||||
? Promise.reject(new StaleSearchError())
|
||||
: Promise.resolve(results);
|
||||
}
|
||||
|
||||
function selectQuery(queryId) {
|
||||
let query = null;
|
||||
if (queryId) {
|
||||
|
||||
@@ -26,7 +26,7 @@ export function TimeAgo({ date, placeholder, autoUpdate }) {
|
||||
const timer = setInterval(forceUpdate, 30 * 1000);
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
}, [autoUpdate]);
|
||||
}, [autoUpdate, forceUpdate]);
|
||||
|
||||
return (
|
||||
<Tooltip title={title}>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function Timer({ from }) {
|
||||
useEffect(() => {
|
||||
const timer = setInterval(forceUpdate, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
}, [forceUpdate]);
|
||||
|
||||
const diff = moment.now() - startTime;
|
||||
const format = diff > 1000 * 60 * 60 ? 'HH:mm:ss' : 'mm:ss'; // no HH under an hour
|
||||
|
||||
@@ -27,7 +27,9 @@ export default function FavoritesDropdown({ fetch, urlTemplate }) {
|
||||
}, [fetch]);
|
||||
|
||||
// fetch items on init
|
||||
useEffect(() => fetchItems(false), []);
|
||||
useEffect(() => {
|
||||
fetchItems(false);
|
||||
}, [fetchItems]);
|
||||
|
||||
// fetch items on click
|
||||
const onVisibleChange = visible => visible && fetchItems();
|
||||
|
||||
@@ -58,22 +58,22 @@ class DynamicForm extends React.Component {
|
||||
|
||||
const hasFilledExtraField = some(props.fields, (field) => {
|
||||
const { extra, initialValue } = field;
|
||||
return extra && (!isEmpty(initialValue) || isNumber(initialValue) || isBoolean(initialValue) && initialValue);
|
||||
return extra && (!isEmpty(initialValue) || isNumber(initialValue) || (isBoolean(initialValue) && initialValue));
|
||||
});
|
||||
|
||||
const inProgressActions = {};
|
||||
props.actions.forEach(action => inProgressActions[action.name] = false);
|
||||
|
||||
this.state = {
|
||||
isSubmitting: false,
|
||||
inProgressActions: [],
|
||||
showExtraFields: hasFilledExtraField,
|
||||
inProgressActions
|
||||
};
|
||||
|
||||
this.actionCallbacks = this.props.actions.reduce((acc, cur) => ({
|
||||
...acc,
|
||||
[cur.name]: cur.callback,
|
||||
}), null);
|
||||
|
||||
props.actions.forEach((action) => {
|
||||
this.state.inProgressActions[action.name] = false;
|
||||
});
|
||||
}
|
||||
|
||||
setActionInProgress = (actionName, inProgress) => {
|
||||
|
||||
@@ -34,7 +34,7 @@ function useGrantees(url) {
|
||||
|
||||
const addPermission = useCallback((userId, accessType = 'modify') => $http.post(
|
||||
url, { access_type: accessType, user_id: userId },
|
||||
).catch(() => notification.error('Could not grant permission to the user'), [url]));
|
||||
).catch(() => notification.error('Could not grant permission to the user')), [url]);
|
||||
|
||||
const removePermission = useCallback((userId, accessType = 'modify') => $http.delete(
|
||||
url, { data: { access_type: accessType, user_id: userId } },
|
||||
@@ -77,7 +77,7 @@ function UserSelect({ onSelect, shouldShowUser }) {
|
||||
useEffect(() => {
|
||||
setLoadingUsers(true);
|
||||
debouncedSearchUsers(searchTerm);
|
||||
}, [searchTerm]);
|
||||
}, [debouncedSearchUsers, searchTerm]);
|
||||
|
||||
return (
|
||||
<Select
|
||||
@@ -117,16 +117,16 @@ function PermissionsEditorDialog({ dialog, author, context, aclUrl }) {
|
||||
.then(setGrantees)
|
||||
.catch(() => notification.error('Failed to load grantees list'))
|
||||
.finally(() => setLoadingGrantees(false));
|
||||
}, []);
|
||||
}, [loadGrantees]);
|
||||
|
||||
const userHasPermission = useCallback(
|
||||
user => (user.id === author.id || !!get(find(grantees, { id: user.id }), 'accessType')),
|
||||
[grantees],
|
||||
[author.id, grantees],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
loadUsersWithPermissions();
|
||||
}, [aclUrl]);
|
||||
}, [aclUrl, loadUsersWithPermissions]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
@@ -71,7 +71,7 @@ class ChangePasswordDialog extends React.Component {
|
||||
notification.success('Saved.');
|
||||
this.props.dialog.close({ success: true });
|
||||
}, (error = {}) => {
|
||||
notification.error(error.data && error.data.message || 'Failed saving.');
|
||||
notification.error((error.data && error.data.message) || 'Failed saving.');
|
||||
this.setState({ updatingPassword: false });
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -112,7 +112,7 @@ export default class UserEdit extends React.Component {
|
||||
successCallback('Saved.');
|
||||
this.setState({ user: User.convertUserInfo(user) });
|
||||
}, (error = {}) => {
|
||||
errorCallback(error.data && error.data.message || 'Failed saving.');
|
||||
errorCallback((error.data && error.data.message) || 'Failed saving.');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ function getQueryResultData(queryResult) {
|
||||
|
||||
export default function useQueryResult(queryResult) {
|
||||
const [data, setData] = useState(getQueryResultData(queryResult));
|
||||
let isCancelled = false;
|
||||
useEffect(() => {
|
||||
let isCancelled = false;
|
||||
if (queryResult) {
|
||||
queryResult.toPromise()
|
||||
.then(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
export default function useSearchResults(
|
||||
@@ -7,17 +7,16 @@ export default function useSearchResults(
|
||||
) {
|
||||
const [result, setResult] = useState(initialResults);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
let currentSearchTerm = null;
|
||||
let isDestroyed = false;
|
||||
const currentSearchTerm = useRef(null);
|
||||
const isDestroyed = useRef(false);
|
||||
|
||||
const [doSearch] = useDebouncedCallback((searchTerm) => {
|
||||
setIsLoading(true);
|
||||
currentSearchTerm = searchTerm;
|
||||
currentSearchTerm.current = searchTerm;
|
||||
fetch(searchTerm)
|
||||
.catch(() => null)
|
||||
.then((data) => {
|
||||
if ((searchTerm === currentSearchTerm) && !isDestroyed) {
|
||||
if ((searchTerm === currentSearchTerm.current) && !isDestroyed.current) {
|
||||
setResult(data);
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -26,7 +25,7 @@ export default function useSearchResults(
|
||||
|
||||
useEffect(() => (
|
||||
// ignore all requests after component destruction
|
||||
() => { isDestroyed = true; }
|
||||
() => { isDestroyed.current = true; }
|
||||
), []);
|
||||
|
||||
return [doSearch, result, isLoading];
|
||||
|
||||
@@ -109,7 +109,7 @@ export default class AlertDestinations extends React.Component {
|
||||
return {
|
||||
content: (
|
||||
<div className="destination-wrapper">
|
||||
<img src={`${IMG_ROOT}/${item.type}.png`} className="destination-icon" alt={name} />
|
||||
<img src={`${IMG_ROOT}/${item.type}.png`} className="destination-icon" alt={item.name} />
|
||||
<span className="flex-fill">{item.name}</span>
|
||||
<ListItemAddon isSelected={isSelected} alreadyInGroup={alreadyInGroup} deselectedIcon="fa-plus" />
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function MenuButton({ doDelete, canEdit, mute, unmute, muted }) {
|
||||
maskClosable: true,
|
||||
autoFocusButton: null,
|
||||
});
|
||||
}, []);
|
||||
}, [doDelete]);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
|
||||
@@ -28,7 +28,7 @@ function RearmByDuration({ value, onChange, editMode }) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
}, [value]);
|
||||
|
||||
if (!isNumber(count) || !isNumber(durationIdx)) {
|
||||
return null;
|
||||
|
||||
@@ -68,7 +68,7 @@ function FavoriteList({ title, resource, itemUrl, emptyState }) {
|
||||
resource.favorites().$promise
|
||||
.then(({ results }) => setItems(results))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
}, [resource]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import qs from 'qs';
|
||||
|
||||
const parse = queryString => qs.parse(queryString, { allowDots: true, ignoreQueryPrefix: true });
|
||||
const onlyParameters = ([k]) => k.startsWith('p_');
|
||||
const removePrefix = ([k, v]) => [k.slice(2), v];
|
||||
const toObject = (obj, [k, v]) => Object.assign(obj, { [k]: v });
|
||||
|
||||
export default () => Object.entries(parse(location.search))
|
||||
.filter(onlyParameters)
|
||||
.map(removePrefix)
|
||||
.reduce(toObject, {});
|
||||
@@ -84,7 +84,7 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
|
||||
originalOptions: options,
|
||||
};
|
||||
},
|
||||
[visualization],
|
||||
[data, isNew, visualization],
|
||||
);
|
||||
|
||||
const [type, setType] = useState(defaultState.type);
|
||||
|
||||
@@ -32,12 +32,12 @@ export function VisualizationRenderer(props) {
|
||||
// Reset local filters when query results updated
|
||||
useEffect(() => {
|
||||
setFilters(combineFilters(data.filters, props.filters));
|
||||
}, [data]);
|
||||
}, [data, props.filters]);
|
||||
|
||||
// Update local filters when global filters changed
|
||||
useEffect(() => {
|
||||
setFilters(combineFilters(filters, props.filters));
|
||||
}, [props.filters]);
|
||||
}, [filters, props.filters]);
|
||||
|
||||
const filteredData = useMemo(() => ({
|
||||
columns: data.columns,
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function SeriesSettings({ options, data, onOptionsChange }) {
|
||||
const seriesOptions = [...series];
|
||||
seriesOptions.splice(newIndex, 0, ...seriesOptions.splice(oldIndex, 1));
|
||||
onOptionsChange({ seriesOptions: fromPairs(map(seriesOptions, ({ key }, zIndex) => ([key, { zIndex }]))) });
|
||||
}, [series]);
|
||||
}, [onOptionsChange, series]);
|
||||
|
||||
const updateSeriesOption = useCallback((key, prop, value) => {
|
||||
onOptionsChange({
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { RendererPropTypes } from '@/visualizations';
|
||||
|
||||
import { clientConfig } from '@/services/auth';
|
||||
import resizeObserver from '@/services/resizeObserver';
|
||||
|
||||
import getChartData from '../getChartData';
|
||||
import { Plotly, prepareCustomChartData, createCustomChartRenderer } from '../plotly';
|
||||
|
||||
export default function CustomPlotlyChart({ options, data }) {
|
||||
if (!clientConfig.allowCustomJSVisualizations) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [container, setContainer] = useState(null);
|
||||
|
||||
const renderCustomChart = useMemo(
|
||||
@@ -33,7 +28,7 @@ export default function CustomPlotlyChart({ options, data }) {
|
||||
});
|
||||
return unwatch;
|
||||
}
|
||||
}, [container, plotlyData]);
|
||||
}, [container, plotlyData, renderCustomChart]);
|
||||
|
||||
// Cleanup when component destroyed
|
||||
useEffect(() => {
|
||||
|
||||
@@ -3,11 +3,12 @@ import { RendererPropTypes } from '@/visualizations';
|
||||
|
||||
import PlotlyChart from './PlotlyChart';
|
||||
import CustomPlotlyChart from './CustomPlotlyChart';
|
||||
import { clientConfig } from '@/services/auth';
|
||||
|
||||
import './renderer.less';
|
||||
|
||||
export default function Renderer({ options, ...props }) {
|
||||
if (options.globalSeriesType === 'custom') {
|
||||
if (options.globalSeriesType === 'custom' && clientConfig.allowCustomJSVisualizations) {
|
||||
return <CustomPlotlyChart options={options} {...props} />;
|
||||
}
|
||||
return <PlotlyChart options={options} {...props} />;
|
||||
|
||||
@@ -66,7 +66,7 @@ function prepareSeries(series, options, additionalOptions) {
|
||||
{ x: plotlySeries.x[j], y: plotlySeries.y[i] },
|
||||
);
|
||||
|
||||
const zValue = datum && datum.zVal || 0;
|
||||
const zValue = (datum && datum.zVal) || 0;
|
||||
item.push(zValue);
|
||||
|
||||
if (isFinite(zMax) && options.showDataLabels) {
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function BoundsSettings({ options, onOptionsChange }) {
|
||||
setBounds(newBounds);
|
||||
onOptionsChangeDebounced({ bounds: newBounds });
|
||||
}
|
||||
}, [bounds]);
|
||||
}, [bounds, onOptionsChangeDebounced]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function Renderer({ data, options, onOptionsChange }) {
|
||||
options, // detect changes for all options except bounds, but pass them all!
|
||||
);
|
||||
}
|
||||
}, [map, geoJson, data, optionsWithoutBounds]);
|
||||
}, [map, geoJson, data, optionsWithoutBounds]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
|
||||
@@ -156,7 +156,7 @@ export default function Cornelius({ data, options }) {
|
||||
const maxRowLength = useMemo(() => min([
|
||||
max(map(data, d => d.length)) || 0,
|
||||
options.maxColumns + 1, // each row includes totals, but `maxColumns` is only for stage columns
|
||||
]), [data]);
|
||||
]), [data, options.maxColumns]);
|
||||
|
||||
if (data.length === 0) {
|
||||
return null;
|
||||
|
||||
@@ -16,6 +16,7 @@ function generateRowKeyPrefix() {
|
||||
|
||||
export default function Renderer({ data, options }) {
|
||||
const funnelData = useMemo(() => prepareData(data.rows, options), [data, options]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const rowKeyPrefix = useMemo(() => generateRowKeyPrefix(), [funnelData]);
|
||||
|
||||
const formatValue = useMemo(() => createNumberFormatter(options.numberFormat), [options.numberFormat]);
|
||||
|
||||
6
client/prettier.config.js
Normal file
6
client/prettier.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
printWidth: 120,
|
||||
jsxBracketSameLine: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'es5',
|
||||
};
|
||||
1224
package-lock.json
generated
1224
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -13,8 +13,10 @@
|
||||
"analyze": "npm run clean && BUNDLE_ANALYZER=on webpack",
|
||||
"analyze:build": "npm run clean && NODE_ENV=production BUNDLE_ANALYZER=on webpack",
|
||||
"lint": "npm run lint:base -- --ext .js --ext .jsx ./client",
|
||||
"lint:fix": "npm run lint:base -- --fix --ext .js --ext .jsx ./client",
|
||||
"lint:base": "eslint --config ./client/.eslintrc.js --ignore-path ./client/.eslintignore",
|
||||
"lint:ci": "npm run lint -- --format junit --output-file /tmp/test-results/eslint/results.xml",
|
||||
"prettier": "prettier --write client/app/**/*.{js,jsx} client/cypress/**/*.js",
|
||||
"test": "TZ=Africa/Khartoum jest",
|
||||
"test:watch": "jest --watch",
|
||||
"cypress:install": "npm install --no-save cypress@~3.6.1 @percy/cypress@^2.2.0 atob@2.1.2",
|
||||
@@ -25,8 +27,8 @@
|
||||
"url": "git+https://github.com/getredash/redash.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.0.0",
|
||||
"npm": "^5.0.0"
|
||||
"node": "^12.0.0",
|
||||
"npm": "^6.0.0"
|
||||
},
|
||||
"author": "Redash Contributors",
|
||||
"license": "BSD-2-Clause",
|
||||
@@ -72,7 +74,6 @@
|
||||
"pace-progress": "git+https://github.com/getredash/pace.git",
|
||||
"plotly.js": "1.41.3",
|
||||
"prop-types": "^15.6.1",
|
||||
"qs": "^6.7.0",
|
||||
"react": "^16.8.3",
|
||||
"react-ace": "^6.1.0",
|
||||
"react-dom": "^16.8.3",
|
||||
@@ -90,7 +91,9 @@
|
||||
"@babel/plugin-transform-object-assign": "^7.2.0",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-jest": "^24.1.0",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-angularjs-annotate": "^0.8.2",
|
||||
@@ -100,18 +103,20 @@
|
||||
"enzyme": "^3.8.0",
|
||||
"enzyme-adapter-react-16": "^1.7.1",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-import-resolver-webpack": "^0.8.3",
|
||||
"eslint-loader": "^2.1.1",
|
||||
"eslint-plugin-chai-friendly": "^0.4.1",
|
||||
"eslint-plugin-compat": "^3.0.1",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-config-prettier": "^6.7.0",
|
||||
"eslint-config-react-app": "^5.1.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-chai-friendly": "^0.5.0",
|
||||
"eslint-plugin-compat": "^3.3.0",
|
||||
"eslint-plugin-cypress": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-flowtype": "^3.13.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-jest": "^22.2.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.2",
|
||||
"eslint-plugin-no-only-tests": "^2.3.1",
|
||||
"eslint-plugin-react": "^7.12.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-no-only-tests": "^2.4.0",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"eslint-plugin-react-hooks": "^1.7.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^1.3.1",
|
||||
@@ -123,6 +128,7 @@
|
||||
"lint-staged": "^8.1.3",
|
||||
"mini-css-extract-plugin": "^0.4.4",
|
||||
"mockdate": "^2.0.2",
|
||||
"prettier": "^1.19.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-test-renderer": "^16.5.2",
|
||||
"request": "^2.88.0",
|
||||
|
||||
Reference in New Issue
Block a user