chore: enable no-unused-vars and prefer-template eslint rules (#10350)

This commit is contained in:
Erik Ritter 2020-07-16 19:13:59 -07:00 committed by GitHub
parent 0eee6785a8
commit 09de805017
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 66 additions and 119 deletions

View File

@ -34,7 +34,7 @@ module.exports = {
rules: {
'import/no-unresolved': 0,
'global-require': 0,
}
},
},
{
files: ['*.ts', '*.tsx'],
@ -88,10 +88,8 @@ module.exports = {
'no-prototype-builtins': 0,
'no-restricted-properties': 0,
'no-restricted-syntax': 0,
'no-unused-vars': 0,
'padded-blocks': 0,
'prefer-arrow-callback': 0,
'prefer-template': 0,
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
'react/jsx-no-bind': 0,
@ -150,11 +148,9 @@ module.exports = {
'no-prototype-builtins': 0,
'no-restricted-properties': 0,
'no-restricted-syntax': 0,
'no-unused-vars': 0,
'padded-blocks': 0,
'prefer-arrow-callback': 0,
'prefer-object-spread': 1,
'prefer-template': 0,
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
'react/jsx-no-bind': 0,

View File

@ -129,7 +129,6 @@
"d3-scale": "^2.1.2",
"dnd-core": "^2.6.0",
"dom-to-image": "^2.6.0",
"dompurify": "^2.0.7",
"geolib": "^2.0.24",
"immutable": "^3.8.2",
"interweave": "^11.2.0",

View File

@ -16,7 +16,5 @@
* specific language governing permissions and limitations
* under the License.
*/
import * as React from 'react';
export default 'SvgrURL';
export const ReactComponent = 'svg';

View File

@ -31,7 +31,7 @@ describe('AnchorLink', () => {
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
value: {
hash: '#' + props.anchorLinkId,
hash: `#${props.anchorLinkId}`,
},
});
});

View File

@ -19,7 +19,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ColumnOption, ColumnTypeLabel } from '@superset-ui/chart-controls';
import { ColumnTypeLabel } from '@superset-ui/chart-controls';
describe('ColumnOption', () => {
const defaultProps = {

View File

@ -20,7 +20,6 @@ import React from 'react';
import { mount, shallow } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { MenuItem } from 'react-bootstrap';
import Select from 'src/components/Select';
import { QueryParamProvider } from 'use-query-params';
import { supersetTheme, ThemeProvider } from '@superset-ui/style';
@ -29,7 +28,6 @@ import ListViewFilters from 'src/components/ListView/Filters';
import ListViewPagination from 'src/components/ListView/Pagination';
import Pagination from 'src/components/Pagination';
import Button from 'src/components/Button';
import { areArraysShallowEqual } from 'src/reduxUtils';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';

View File

@ -20,12 +20,7 @@
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import {
Select,
AsyncSelect,
OnPasteSelect,
CreatableSelect,
} from 'src/components/Select';
import { Select, OnPasteSelect, CreatableSelect } from 'src/components/Select';
const defaultProps = {
onChange: sinon.spy(),

View File

@ -21,7 +21,7 @@ import { mount, shallow } from 'enzyme';
import ModalTrigger from 'src/components/ModalTrigger';
import RefreshIntervalModal from 'src/dashboard/components/RefreshIntervalModal';
import { Modal, Alert } from 'react-bootstrap';
import { Alert } from 'react-bootstrap';
describe('RefreshIntervalModal', () => {
const mockedProps = {

View File

@ -66,7 +66,6 @@ describe('ExploreChartHeader', () => {
});
it('should update title but not save', () => {
const newTitle = 'New Chart Title';
const editableTitle = wrapper.find(EditableTitle);
expect(editableTitle.props().onSaveTitle).toBe(updateChartTitleStub);
});

View File

@ -85,7 +85,7 @@ describe('MetricsControl', () => {
{ optionName: '_col_value', type: 'DOUBLE', column_name: 'value' },
...Object.keys(AGGREGATES).map(aggregate => ({
aggregate_name: aggregate,
optionName: '_aggregate_' + aggregate,
optionName: `_aggregate_${aggregate}`,
})),
{
optionName: 'sum__value',

View File

@ -19,7 +19,6 @@
import { Alert } from 'react-bootstrap';
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import Toast from 'src/messageToasts/components/Toast';
import mockMessageToasts from '../mockMessageToasts';

View File

@ -18,7 +18,6 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { Checkbox } from 'react-bootstrap';
import {
SQL_EDITOR_GUTTER_HEIGHT,
SQL_EDITOR_GUTTER_MARGIN,

View File

@ -58,7 +58,7 @@ describe('async actions', () => {
);
const runQueryEndpoint = 'glob:*/superset/sql_json/*';
fetchMock.post(runQueryEndpoint, '{ "data": ' + mockBigNumber + ' }');
fetchMock.post(runQueryEndpoint, `{ "data": ${mockBigNumber} }`);
describe('saveQuery', () => {
const saveQueryEndpoint = 'glob:*/savedqueryviewapi/api/create';

View File

@ -213,7 +213,7 @@ export default class CRUDCollection extends React.PureComponent {
];
if (isExpanded) {
trs.push(
<tr className="exp" key={'exp__' + record.id}>
<tr className="exp" key={`exp__${record.id}`}>
<td
colSpan={this.effectiveTableColumns().length}
className="expanded"

View File

@ -428,7 +428,7 @@ export function postStopQuery(query) {
.then(() => dispatch(addSuccessToast(t('Query was stopped.'))))
.catch(() =>
dispatch(
addDangerToast(t('Failed at stopping query. ') + `'${query.id}'`),
addDangerToast(`${t('Failed at stopping query. ')}'${query.id}'`),
),
);
};
@ -1230,7 +1230,7 @@ export function popDatasourceQuery(datasourceKey, sql) {
.then(({ json }) =>
dispatch(
addQueryEditor({
title: 'Query ' + json.name,
title: `Query ${json.name}`,
dbId: json.database.id,
schema: json.schema,
autorun: sql !== undefined,

View File

@ -60,7 +60,7 @@ class HighlightedSql extends React.Component {
return lines
.map(line => {
if (line.length > this.props.maxWidth) {
return line.slice(0, this.props.maxWidth) + '{...}';
return `${line.slice(0, this.props.maxWidth)}{...}`;
}
return line;
})

View File

@ -136,7 +136,7 @@ class QuerySearch extends React.PureComponent {
const validParams = params.filter(function (p) {
return p !== '';
});
return baseUrl + '?' + validParams.join('&');
return `${baseUrl}?${validParams.join('&')}`;
}
changeStatus(status) {
@ -150,7 +150,7 @@ class QuerySearch extends React.PureComponent {
userLabel(user) {
if (user.first_name && user.last_name) {
return user.first_name + ' ' + user.last_name;
return `${user.first_name} ${user.last_name}`;
}
return user.username;
}

View File

@ -167,7 +167,7 @@ export default class ResultSet extends React.PureComponent<
{this.props.csv && (
<Button
bsSize="small"
href={'/superset/csv/' + this.props.query.id}
href={`/superset/csv/${this.props.query.id}`}
>
<i className="fa fa-file-text-o" /> {t('.CSV')}
</Button>

View File

@ -77,10 +77,9 @@ class ShareSqlLabQuery extends React.Component {
let savedQueryToastContent;
if (this.props.queryEditor.remoteId) {
savedQueryToastContent =
window.location.origin +
window.location.pathname +
`?savedQueryId=${this.props.queryEditor.remoteId}`;
savedQueryToastContent = `${
window.location.origin + window.location.pathname
}?savedQueryId=${this.props.queryEditor.remoteId}`;
this.setState({ shortUrl: savedQueryToastContent });
} else {
savedQueryToastContent = t('Please save the query to enable sharing');

View File

@ -20,7 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
export default function TabStatusIcon(props) {
return <div className={'circle ' + props.tabState} />;
return <div className={`circle ${props.tabState}`} />;
}
TabStatusIcon.propTypes = {

View File

@ -304,7 +304,7 @@ class TabbedSqlEditors extends React.PureComponent {
{isSelected && (
<DropdownButton
bsSize="small"
id={'ddbtn-tab-' + i}
id={`ddbtn-tab-${i}`}
title={' '}
noCaret
>

View File

@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
import dompurify from 'dompurify';
import { snakeCase } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';

View File

@ -18,7 +18,6 @@
*/
/* eslint no-undef: 'error' */
/* eslint no-param-reassign: ["error", { "props": false }] */
import URI from 'urijs';
import moment from 'moment';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
@ -26,7 +25,6 @@ import { isFeatureEnabled, FeatureFlag } from '../featureFlags';
import {
getAnnotationJsonUrl,
getExploreUrl,
getHostName,
getLegacyEndpointType,
buildV1ChartDataPayload,
postForm,

View File

@ -89,17 +89,14 @@ export default function chartReducer(charts = {}, action) {
return {
...state,
chartStatus: 'failed',
chartAlert:
`${t('Query timeout')} - ` +
t(
`visualization queries are set to timeout at ${action.timeout} seconds. `,
) +
t(
'Perhaps your data has grown, your database is under unusual load, ' +
'or you are simply querying a data source that is too large ' +
'to be processed within the timeout range. ' +
'If that is the case, we recommend that you summarize your data further.',
),
chartAlert: `${t('Query timeout')} - ${t(
`visualization queries are set to timeout at ${action.timeout} seconds. `,
)}${t(
'Perhaps your data has grown, your database is under unusual load, ' +
'or you are simply querying a data source that is too large ' +
'to be processed within the timeout range. ' +
'If that is the case, we recommend that you summarize your data further.',
)}`,
chartUpdateEndTime: now(),
};
},

View File

@ -20,7 +20,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Table, Tr, Td, Thead, Th } from 'reactable-arc';
import { isEqual, isEmpty } from 'lodash';
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import getControlsForVizType from 'src/utils/getControlsForVizType';
import { t } from '@superset-ui/translation';
import TooltipWrapper from './TooltipWrapper';

View File

@ -143,7 +143,7 @@ export default class FilterableTable extends PureComponent<
this.complexColumns = props.orderedColumnKeys.reduce(
(obj, key) => ({
...obj,
[key]: props.expandedColumns.some(name => name.startsWith(key + '.')),
[key]: props.expandedColumns.some(name => name.startsWith(`${key}.`)),
}),
{},
);

View File

@ -44,7 +44,7 @@ const Link = ({
href={href}
onClick={onClick}
style={style}
className={'Link ' + className}
className={`Link ${className}`}
>
{children}
</a>

View File

@ -36,7 +36,7 @@ export default function PopoverSection({
info,
}) {
return (
<div className={'PopoverSection ' + (!isSelected ? 'dimmed' : '')}>
<div className={`PopoverSection ${!isSelected ? 'dimmed' : ''}`}>
<div onClick={onSelect} className="pointer">
<strong>{title}</strong> &nbsp;
{info && (

View File

@ -90,9 +90,9 @@ class TableLoader extends React.PureComponent {
{this.state.data.map((row, i) => (
<Tr key={i}>
{columns.map(col => {
if (row.hasOwnProperty('_' + col)) {
if (row.hasOwnProperty(`_${col}`)) {
return (
<Td key={col} column={col} value={row['_' + col]}>
<Td key={col} column={col} value={row[`_${col}`]}>
{row[col]}
</Td>
);

View File

@ -103,11 +103,11 @@ export default class TableSelector extends React.PureComponent {
});
}
onDatabaseChange(db, selectChangeMeta) {
return this.changeDataBase(db);
onDatabaseChange(db, force) {
return this.changeDataBase(db, force);
}
onSchemaChange(schemaOpt, selectActionMeta) {
onSchemaChange(schemaOpt) {
return this.changeSchema(schemaOpt);
}

View File

@ -16,8 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
const { assign } = Object;
const A11Y_BABU = '#00A699';
const AXIS_LINE_GRAY = '#484848';

View File

@ -167,7 +167,7 @@ class Markdown extends React.PureComponent {
}
}
componentDidCatch(error, info) {
componentDidCatch() {
if (this.state.editor && this.state.editorMode === 'preview') {
this.props.addDangerToast(
t(

View File

@ -144,7 +144,7 @@ export default class AdhocFilter {
getDefaultLabel() {
const label = this.translateToSql();
return label.length < 43 ? label : label.substring(0, 40) + '...';
return label.length < 43 ? label : `${label.substring(0, 40)}...`;
}
translateToSql() {

View File

@ -87,7 +87,7 @@ export default class AdhocMetric {
getDefaultLabel() {
const label = this.translateToSql();
return label.length < 43 ? label : label.substring(0, 40) + '...';
return label.length < 43 ? label : `${label.substring(0, 40)}...`;
}
translateToSql() {

View File

@ -19,7 +19,7 @@
import React from 'react';
import { hot } from 'react-hot-loader/root';
import { Provider } from 'react-redux';
import { styled, supersetTheme, ThemeProvider } from '@superset-ui/style';
import { supersetTheme, ThemeProvider } from '@superset-ui/style';
import ToastPresenter from '../messageToasts/containers/ToastPresenter';
import ExploreViewContainer from './components/ExploreViewContainer';
import setupApp from '../setup/setupApp';

View File

@ -19,7 +19,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormGroup } from 'react-bootstrap';
import { Select, Creatable } from 'src/components/Select';
import { Select } from 'src/components/Select';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
@ -236,7 +236,7 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
loading: false,
}));
})
.catch(error => {
.catch(() => {
this.setState(() => ({
suggestions: [],
abortActiveRequest: null,

View File

@ -144,7 +144,7 @@ export default class AdhocFilterEditPopoverSqlTabContent extends React.Component
ref={this.handleAceEditorRef}
mode="sql"
theme="github"
height={height - 100 + 'px'}
height={`${height - 100}px`}
onChange={this.onSqlExpressionChange}
width="100%"
showGutter={false}

View File

@ -35,7 +35,7 @@ import 'brace/ext/language_tools';
import { t } from '@superset-ui/translation';
import { ColumnOption } from '@superset-ui/chart-controls';
import { AGGREGATES, AGGREGATES_OPTIONS } from '../constants';
import { AGGREGATES_OPTIONS } from '../constants';
import AdhocMetricEditPopoverTitle from './AdhocMetricEditPopoverTitle';
import columnType from '../propTypes/columnType';
import AdhocMetric, { EXPRESSION_TYPES } from '../AdhocMetric';
@ -283,7 +283,7 @@ export default class AdhocMetricEditPopover extends React.Component {
ref={this.handleAceEditorRef}
mode="sql"
theme="github"
height={this.state.height - 43 + 'px'}
height={`${this.state.height - 43}px`}
onChange={this.onSqlExpressionChange}
width="100%"
showGutter={false}

View File

@ -19,7 +19,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Label, OverlayTrigger } from 'react-bootstrap';
import { thresholdScott } from 'd3-array';
import AdhocMetricEditPopover from './AdhocMetricEditPopover';
import AdhocMetric from '../AdhocMetric';

View File

@ -22,10 +22,7 @@ import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Alert, Tab, Tabs } from 'react-bootstrap';
import { isPlainObject } from 'lodash';
import { t } from '@superset-ui/translation';
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import { sharedControls } from '@superset-ui/chart-controls';
import ControlPanelSection from './ControlPanelSection';
import ControlRow from './ControlRow';
@ -64,7 +61,7 @@ class ControlPanelsContainer extends React.Component {
}
renderControl({ name, config }) {
const { actions, controls, exploreState, form_data: formData } = this.props;
const { actions, controls, form_data: formData } = this.props;
const { visibility } = config;
// If the control item is not an object, we have to look up the control data from

View File

@ -47,10 +47,10 @@ export default class EmbedCodeButton extends React.Component {
}
generateEmbedHTML() {
const srcLink =
const srcLink = `${
window.location.origin +
getExploreLongUrl(this.props.latestQueryFormData, 'standalone') +
`&height=${this.state.height}`;
getExploreLongUrl(this.props.latestQueryFormData, 'standalone')
}&height=${this.state.height}`;
return (
'<iframe\n' +
` width="${this.state.width}"\n` +

View File

@ -180,7 +180,7 @@ class ExploreViewContainer extends React.Component {
getHeight() {
if (this.props.forcedHeight) {
return this.props.forcedHeight + 'px';
return `${this.props.forcedHeight}px`;
}
const navHeight = this.props.standalone ? 0 : 90;
return `${window.innerHeight - navHeight}px`;

View File

@ -245,12 +245,12 @@ export default class AdhocFilterControl extends React.Component {
} else if (option.column_name) {
results.push({
...option,
filterOptionName: '_col_' + option.column_name,
filterOptionName: `_col_${option.column_name}`,
});
} else if (option instanceof AdhocMetric) {
results.push({
...option,
filterOptionName: '_adhocmetric_' + option.label,
filterOptionName: `_adhocmetric_${option.label}`,
});
}
return results;

View File

@ -23,7 +23,6 @@ import {
DropdownButton,
FormControl,
FormGroup,
Glyphicon,
InputGroup,
Label,
MenuItem,
@ -124,7 +123,7 @@ function getStateFromSeparator(value) {
}
function getStateFromCommonTimeFrame(value) {
const units = value.split(' ')[1] + 's';
const units = `${value.split(' ')[1]}s`;
return {
tab: TABS.DEFAULTS,
type: TYPES.DEFAULTS,

View File

@ -279,11 +279,11 @@ export default class MetricsControl extends React.PureComponent {
if (option.metric_name) {
results.push({ ...option, optionName: option.metric_name });
} else if (option.column_name) {
results.push({ ...option, optionName: '_col_' + option.column_name });
results.push({ ...option, optionName: `_col_${option.column_name}` });
} else if (option.aggregate_name) {
results.push({
...option,
optionName: '_aggregate_' + option.aggregate_name,
optionName: `_aggregate_${option.aggregate_name}`,
});
}
return results;

View File

@ -76,7 +76,7 @@ const SelectAsyncControl = props => {
dataEndpoint={dataEndpoint}
onChange={onSelectionChange}
onAsyncError={errorMsg =>
this.props.addDangerToast(onAsyncErrorMessage + ': ' + errorMsg)
this.props.addDangerToast(`${onAsyncErrorMessage}: ${errorMsg}`)
}
mutator={mutator}
multi={multi}

View File

@ -139,7 +139,6 @@ export default class SelectControl extends React.PureComponent {
} else if (props.choices) {
// Accepts different formats of input
options = props.choices.map(c => {
let option;
if (Array.isArray(c)) {
const [value, label] = c.length > 1 ? c : [c[0], c[0]];
return { label, [props.valueKey]: value };

View File

@ -85,11 +85,9 @@ export default class ViewportControl extends React.Component {
}
renderLabel() {
if (this.props.value.longitude && this.props.value.latitude) {
return (
decimal2sexagesimal(this.props.value.longitude) +
' | ' +
decimal2sexagesimal(this.props.value.latitude)
);
return `${decimal2sexagesimal(
this.props.value.longitude,
)} | ${decimal2sexagesimal(this.props.value.latitude)}`;
}
return 'N/A';
}

View File

@ -17,10 +17,7 @@
* under the License.
*/
import { t } from '@superset-ui/translation';
import {
formatSelectOptions,
formatSelectOptionsForRange,
} from '../../modules/utils';
import { formatSelectOptions } from '../../modules/utils';
import {
filterNulls,
autozoom,

View File

@ -22,19 +22,10 @@
import React from 'react';
import { t } from '@superset-ui/translation';
import { validateNonEmpty } from '@superset-ui/validator';
import { ColumnOption, sharedControls } from '@superset-ui/chart-controls';
import { sharedControls } from '@superset-ui/chart-controls';
import { D3_FORMAT_OPTIONS, columnChoices, PRIMARY_COLOR } from '../controls';
import { DEFAULT_VIEWPORT } from '../../explore/components/controls/ViewportControl';
const timeColumnOption = {
verbose_name: 'Time',
column_name: '__timestamp',
description: t(
'A reference to the [Time] configuration, taking granularity into ' +
'account',
),
};
const sandboxUrl =
'https://github.com/apache/incubator-superset/' +
'blob/master/superset-frontend/src/modules/sandbox.js';

View File

@ -19,7 +19,6 @@
import memoizeOne from 'memoize-one';
import { getChartControlPanelRegistry } from '@superset-ui/chart';
import { expandControlConfig } from '@superset-ui/chart-controls';
import { controls as SHARED_CONTROLS } from './controls';
import * as SECTIONS from './controlPanels/sections';
export function getFormDataFromControls(controlsState) {

View File

@ -68,11 +68,7 @@ import {
} from '@superset-ui/validator';
import { ColumnOption } from '@superset-ui/chart-controls';
import {
formatSelectOptionsForRange,
formatSelectOptions,
mainMetric,
} from '../modules/utils';
import { formatSelectOptions, mainMetric } from '../modules/utils';
import { TIME_FILTER_LABELS } from './constants';
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
@ -136,7 +132,7 @@ const groupByControl = {
valueRenderer: c => <ColumnOption column={c} />,
valueKey: 'column_name',
allowAll: true,
filterOption: ({ label, value, data: opt }, text) =>
filterOption: ({ data: opt }, text) =>
(opt.column_name &&
opt.column_name.toLowerCase().indexOf(text.toLowerCase()) >= 0) ||
(opt.verbose_name &&

View File

@ -18,7 +18,6 @@
*/
/* eslint camelcase: 0 */
import URI from 'urijs';
import { SupersetClient } from '@superset-ui/connection';
import { buildQueryContext } from '@superset-ui/query';
import { availableDomains } from 'src/utils/hostNamesConfig';
import { safeStringify } from 'src/utils/safeStringify';

View File

@ -64,7 +64,7 @@ export function getDatasourceParameter(datasourceId, datasourceType) {
export function getParam(name) {
/* eslint no-useless-escape: 0 */
const formattedName = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + formattedName + '=([^&#]*)');
const regex = new RegExp(`[\\?&]${formattedName}=([^&#]*)`);
const results = regex.exec(location.search);
return results === null
? ''

View File

@ -31,7 +31,7 @@ function showApiMessage(resp: ClientErrorObject) {
'data-dismiss="alert">\xD7</button> </div>';
const severity = resp.severity || 'info';
$(template)
.addClass('alert-' + severity)
.addClass(`alert-${severity}`)
.append(resp.message || '')
.appendTo($('#alert-container'));
}
@ -58,7 +58,7 @@ export default function setupApp() {
const $this = $(this);
const prefix = $this.data('checkbox-api-prefix');
const id = $this.attr('id');
toggleCheckbox(prefix, '#' + id);
toggleCheckbox(prefix, `#${id}`);
});
// for language picker dropdown

View File

@ -117,7 +117,7 @@ export function optionFromValue(opt) {
export function prepareCopyToClipboardTabularData(data) {
let result = '';
for (let i = 0; i < data.length; ++i) {
result += Object.values(data[i]).join('\t') + '\n';
result += `${Object.values(data[i]).join('\t')}\n`;
}
return result;
}