fix: lint errors (#420)

* fix: lint errors

* fix: lint

* fix: more lints

* fix: broken test

* fix: singleton type
This commit is contained in:
Krist Wongsuphasawat 2020-04-30 13:50:06 -07:00 committed by Yongjie Zhao
parent 619045048f
commit 62b2a50faf
36 changed files with 70 additions and 48 deletions

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { isDefined } from '@superset-ui/core'; import { isDefined } from '@superset-ui/core';
function checkNumber(input: any): input is number { function checkNumber(input: unknown): input is number {
return isDefined(input) && typeof input === 'number'; return isDefined(input) && typeof input === 'number';
} }

View File

@ -29,6 +29,7 @@ describe('WithLegend', () => {
<WithLegend debounceTime={1} width={500} height={500} renderChart={renderChart} />, <WithLegend debounceTime={1} width={500} height={500} renderChart={renderChart} />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {
@ -49,6 +50,7 @@ describe('WithLegend', () => {
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {
@ -64,6 +66,7 @@ describe('WithLegend', () => {
<WithLegend debounceTime={1} renderChart={renderChart} renderLegend={renderLegend} />, <WithLegend debounceTime={1} renderChart={renderChart} renderLegend={renderLegend} />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {
@ -84,6 +87,7 @@ describe('WithLegend', () => {
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {
@ -104,6 +108,7 @@ describe('WithLegend', () => {
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {
@ -124,6 +129,7 @@ describe('WithLegend', () => {
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {
@ -144,6 +150,7 @@ describe('WithLegend', () => {
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {
@ -165,6 +172,7 @@ describe('WithLegend', () => {
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
// Have to delay more than debounceTime (1ms) // Have to delay more than debounceTime (1ms)
return promiseTimeout(() => { return promiseTimeout(() => {

View File

@ -56,7 +56,7 @@ export default class ChartClient {
...options, ...options,
} as RequestConfig) } as RequestConfig)
.then(response => response.json as Json) .then(response => response.json as Json)
.then(json => json.form_data); .then(json => json.form_data as QueryFormData);
/* /*
* If formData is also specified, override API result * If formData is also specified, override API result

View File

@ -102,7 +102,7 @@ class ChartDataProvider extends React.PureComponent<Props, State> {
.then(this.handleReceiveData) .then(this.handleReceiveData)
.catch(this.handleError); .catch(this.handleError);
} catch (error) { } catch (error) {
this.handleError(error); this.handleError(error as Error);
} }
}); });
}; };

View File

@ -127,6 +127,7 @@ export default class SuperChart extends React.PureComponent<Props, {}> {
if ( if (
queryData == null || queryData == null ||
queryData.data === null || queryData.data === null ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
(Array.isArray(queryData.data) && queryData.data.length === 0) (Array.isArray(queryData.data) && queryData.data.length === 0)
) { ) {
chart = <NoResultsComponent id={id} className={className} height={height} width={width} />; chart = <NoResultsComponent id={id} className={className} height={height} width={width} />;

View File

@ -9,7 +9,9 @@ import { ChartType } from '../models/ChartPlugin';
import { PreTransformProps, TransformProps, PostTransformProps } from '../types/TransformFunction'; import { PreTransformProps, TransformProps, PostTransformProps } from '../types/TransformFunction';
import { HandlerFunction } from '../types/Base'; import { HandlerFunction } from '../types/Base';
const IDENTITY = (x: any) => x; function IDENTITY<T>(x: T) {
return x;
}
const EMPTY = () => null; const EMPTY = () => null;
@ -24,7 +26,7 @@ const defaultProps = {
}; };
interface LoadingProps { interface LoadingProps {
error: any; error: { toString(): string };
} }
interface LoadedModules { interface LoadedModules {

View File

@ -36,7 +36,9 @@ export default function createLoadableRenderer<Props, Exports>(
const { onRenderFailure, onRenderSuccess } = this.props; const { onRenderFailure, onRenderSuccess } = this.props;
if (!loading) { if (!loading) {
if (error) { if (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
(onRenderFailure as Function)(error); (onRenderFailure as Function)(error);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
} else if (loaded && Object.keys(loaded).length > 0) { } else if (loaded && Object.keys(loaded).length > 0) {
(onRenderSuccess as Function)(); (onRenderSuccess as Function)();
} }

View File

@ -1 +1,2 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ChartControlPanel = { [key: string]: any }; export type ChartControlPanel = { [key: string]: any };

View File

@ -97,12 +97,12 @@ export default class ChartPlugin<T extends QueryFormData = QueryFormData> extend
register() { register() {
const { key = isRequired('config.key') } = this.config; const { key = isRequired('config.key') } = this.config;
getChartMetadataRegistry().registerValue(key, this.metadata); getChartMetadataRegistry().registerValue(key as string, this.metadata);
getChartComponentRegistry().registerLoader(key, this.loadChart); getChartComponentRegistry().registerLoader(key as string, this.loadChart);
getChartControlPanelRegistry().registerValue(key, this.controlPanel); getChartControlPanelRegistry().registerValue(key as string, this.controlPanel);
getChartTransformPropsRegistry().registerLoader(key, this.loadTransformProps); getChartTransformPropsRegistry().registerLoader(key as string, this.loadTransformProps);
if (this.loadBuildQuery) { if (this.loadBuildQuery) {
getChartBuildQueryRegistry().registerLoader(key, this.loadBuildQuery); getChartBuildQueryRegistry().registerLoader(key as string, this.loadBuildQuery);
} }
return this; return this;
@ -110,16 +110,16 @@ export default class ChartPlugin<T extends QueryFormData = QueryFormData> extend
unregister() { unregister() {
const { key = isRequired('config.key') } = this.config; const { key = isRequired('config.key') } = this.config;
getChartMetadataRegistry().remove(key); getChartMetadataRegistry().remove(key as string);
getChartComponentRegistry().remove(key); getChartComponentRegistry().remove(key as string);
getChartControlPanelRegistry().remove(key); getChartControlPanelRegistry().remove(key as string);
getChartTransformPropsRegistry().remove(key); getChartTransformPropsRegistry().remove(key as string);
getChartBuildQueryRegistry().remove(key); getChartBuildQueryRegistry().remove(key as string);
return this; return this;
} }
configure(config: { [key: string]: any }, replace?: boolean) { configure(config: { [key: string]: unknown }, replace?: boolean) {
super.configure(config, replace); super.configure(config, replace);
return this; return this;

View File

@ -92,7 +92,7 @@ export default class ChartProps {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.annotationData = annotationData; this.annotationData = annotationData;
this.datasource = convertKeysToCamelCase(datasource); this.datasource = convertKeysToCamelCase(datasource) as Datasource;
this.rawDatasource = datasource; this.rawDatasource = datasource;
this.formData = convertKeysToCamelCase(formData); this.formData = convertKeysToCamelCase(formData);
this.rawFormData = formData; this.rawFormData = formData;

View File

@ -1,5 +1,6 @@
export type HandlerFunction = (...args: any[]) => void; export type HandlerFunction = (...args: unknown[]) => void;
export interface PlainObject { export interface PlainObject {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any; [key: string]: any;
} }

View File

@ -2,6 +2,7 @@ import { QueryFormData, QueryContext } from '@superset-ui/query';
import ChartProps from '../models/ChartProps'; import ChartProps from '../models/ChartProps';
export interface PlainProps { export interface PlainProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any; [key: string]: any;
} }

View File

@ -225,6 +225,7 @@ describe('SuperChart', () => {
height="100%" height="100%"
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
return promiseTimeout(() => { return promiseTimeout(() => {
@ -243,6 +244,7 @@ describe('SuperChart', () => {
height="125" height="125"
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver([{ contentRect: { height: 125, width: 150 } }]); triggerResizeObserver([{ contentRect: { height: 125, width: 150 } }]);
return promiseTimeout(() => { return promiseTimeout(() => {
@ -264,6 +266,7 @@ describe('SuperChart', () => {
height="25%" height="25%"
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver([{ contentRect: { height: 75, width: 50 } }]); triggerResizeObserver([{ contentRect: { height: 75, width: 50 } }]);
return promiseTimeout(() => { return promiseTimeout(() => {
@ -283,6 +286,7 @@ describe('SuperChart', () => {
debounceTime={1} debounceTime={1}
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
return promiseTimeout(() => { return promiseTimeout(() => {
@ -336,6 +340,7 @@ describe('SuperChart', () => {
Wrapper={MyWrapper} Wrapper={MyWrapper}
/>, />,
); );
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
triggerResizeObserver(); triggerResizeObserver();
return promiseTimeout(() => { return promiseTimeout(() => {

View File

@ -27,7 +27,7 @@ describe('reactify(renderFn)', () => {
const TheChart = reactify(renderFn); const TheChart = reactify(renderFn);
const TheChartWithWillUnmountHook = reactify(renderFn, { componentWillUnmount: willUnmountCb }); const TheChartWithWillUnmountHook = reactify(renderFn, { componentWillUnmount: willUnmountCb });
class TestComponent extends React.PureComponent<{}, { content: string }, any> { class TestComponent extends React.PureComponent<{}, { content: string }> {
constructor(props = {}) { constructor(props = {}) {
super(props); super(props);
this.state = { content: 'abc' }; this.state = { content: 'abc' };
@ -46,7 +46,7 @@ describe('reactify(renderFn)', () => {
} }
} }
class AnotherTestComponent extends React.PureComponent<{}, {}, any> { class AnotherTestComponent extends React.PureComponent<{}, {}> {
render() { render() {
return <TheChartWithWillUnmountHook id="another_test" />; return <TheChartWithWillUnmountHook id="another_test" />;
} }
@ -99,7 +99,9 @@ describe('reactify(renderFn)', () => {
}); });
it('does not try to render if not mounted', () => { it('does not try to render if not mounted', () => {
const anotherRenderFn = jest.fn(); const anotherRenderFn = jest.fn();
const AnotherChart = reactify(anotherRenderFn) as any; // enables valid new AnotherChart() call const AnotherChart = reactify(anotherRenderFn); // enables valid new AnotherChart() call
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
new AnotherChart({ id: 'test' }).execute(); new AnotherChart({ id: 'test' }).execute();
expect(anotherRenderFn).not.toHaveBeenCalled(); expect(anotherRenderFn).not.toHaveBeenCalled();
}); });

View File

@ -32,7 +32,7 @@ describe('ChartProps', () => {
formData: RAW_FORM_DATA, formData: RAW_FORM_DATA,
queryData: QUERY_DATA, queryData: QUERY_DATA,
}); });
expect(props.formData.someField).toEqual(1); expect(props.formData.someField as number).toEqual(1);
expect(props.datasource.columnFormats).toEqual(RAW_DATASOURCE.column_formats); expect(props.datasource.columnFormats).toEqual(RAW_DATASOURCE.column_formats);
expect(props.rawFormData).toEqual(RAW_FORM_DATA); expect(props.rawFormData).toEqual(RAW_FORM_DATA);
expect(props.rawDatasource).toEqual(RAW_DATASOURCE); expect(props.rawDatasource).toEqual(RAW_DATASOURCE);

View File

@ -10,7 +10,7 @@ describe('rejectAfterTimeout()', () => {
rejectAfterTimeout(10) rejectAfterTimeout(10)
.then(throwIfCalled) .then(throwIfCalled)
.catch(error => { .catch((error: Error) => {
expect(error).toBeDefined(); expect(error).toBeDefined();
return done(); return done();

View File

@ -1,3 +1,3 @@
export default function throwIfCalled(args: any) { export default function throwIfCalled(args: unknown) {
throw new Error(`Unexpected call to throwIfCalled(): ${JSON.stringify(args)}`); throw new Error(`Unexpected call to throwIfCalled(): ${JSON.stringify(args)}`);
} }

View File

@ -1,4 +1,5 @@
interface PlainObject { interface PlainObject {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any; [key: string]: any;
} }

View File

@ -2,12 +2,13 @@ import camelCase from 'lodash/camelCase';
import isPlainObject from 'lodash/isPlainObject'; import isPlainObject from 'lodash/isPlainObject';
import mapKeys from 'lodash/mapKeys'; import mapKeys from 'lodash/mapKeys';
export default function convertKeysToCamelCase(object: any) { export default function convertKeysToCamelCase<T>(object: T) {
if (object === null || object === undefined) { if (object === null || object === undefined) {
return object; return object;
} }
if (isPlainObject(object)) { if (isPlainObject(object)) {
return mapKeys(object, (_, k) => camelCase(k)); // eslint-disable-next-line @typescript-eslint/no-explicit-any
return mapKeys(object as { [key: string]: any }, (_, k) => camelCase(k)) as T;
} }
throw new Error(`Cannot convert input that is not a plain object: ${object}`); throw new Error(`Cannot convert input that is not a plain object: ${object}`);
} }

View File

@ -1,3 +1,3 @@
export default function isDefined(x: any) { export default function isDefined(x: unknown) {
return x !== null && x !== undefined; return x !== null && x !== undefined;
} }

View File

@ -1,8 +1,11 @@
interface ClassInterface<T> { interface ClassInterface<T, Args extends unknown[]> {
new (...args: any[]): T; new (...args: Args): T;
} }
export default function makeSingleton<T>(BaseClass: ClassInterface<T>, ...args: any[]): () => T { export default function makeSingleton<T, Args extends unknown[]>(
BaseClass: ClassInterface<T, Args>,
...args: Args
): () => T {
let singleton: T; let singleton: T;
return function getInstance() { return function getInstance() {

View File

@ -1,11 +1,11 @@
/** setTimeout that returns a promise */ /** setTimeout that returns a promise */
export default function promiseTimeout( export default function promiseTimeout<T>(
/** A function to be executed after the timer expires. */ /** A function to be executed after the timer expires. */
func: Function, func: (...args: unknown[]) => T,
/** The time, in milliseconds (thousandths of a second), the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, as soon as possible. */ /** The time, in milliseconds (thousandths of a second), the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, as soon as possible. */
delay?: number, delay?: number,
) { ) {
return new Promise(resolve => { return new Promise<T>(resolve => {
setTimeout(() => { setTimeout(() => {
resolve(func()); resolve(func());
}, delay); }, delay);

View File

@ -39,7 +39,7 @@ describe('Preset', () => {
class Plugin4 extends Plugin { class Plugin4 extends Plugin {
register() { register() {
const { key } = this.config; const { key } = this.config;
values.push(key); values.push(key as number);
return this; return this;
} }

View File

@ -159,7 +159,7 @@ describe('Registry', () => {
it('returns a rejected promise if the item with specified key does not exist', () => { it('returns a rejected promise if the item with specified key does not exist', () => {
const registry = new Registry(); const registry = new Registry();
return registry.getAsPromise('a').then(null, err => { return registry.getAsPromise('a').then(null, (err: Error) => {
expect(err.toString()).toEqual('Error: Item with key "a" is not registered.'); expect(err.toString()).toEqual('Error: Item with key "a" is not registered.');
}); });
}); });

View File

@ -6,8 +6,8 @@ describe('makeSingleton()', () => {
isSitting?: boolean; isSitting?: boolean;
constructor(name: string) { constructor(name?: string) {
this.name = name; this.name = name || 'Pluto';
} }
sit() { sit() {

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
/* eslint-disable func-names, no-plusplus, react/sort-prop-types */ /* eslint-disable func-names, react/sort-prop-types */
import d3 from 'd3'; import d3 from 'd3';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import 'd3-svg-legend'; import 'd3-svg-legend';

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
/* eslint-disable no-continue, no-plusplus, no-bitwise */ /* eslint-disable no-continue, no-bitwise */
/* eslint-disable react/jsx-sort-default-props */ /* eslint-disable react/jsx-sort-default-props */
/* eslint-disable react/sort-prop-types */ /* eslint-disable react/sort-prop-types */
import React from 'react'; import React from 'react';

View File

@ -150,7 +150,6 @@ class ScatterPlotGlowOverlay extends React.PureComponent {
ctx.beginPath(); ctx.beginPath();
if (location.get('properties').get('cluster')) { if (location.get('properties').get('cluster')) {
let clusterLabel = clusterLabelMap[i]; let clusterLabel = clusterLabelMap[i];
// eslint-disable-next-line no-restricted-properties, unicorn/prefer-exponentiation-operator
const scaledRadius = roundDecimal((clusterLabel / maxLabel) ** 0.5 * radius, 1); const scaledRadius = roundDecimal((clusterLabel / maxLabel) ** 0.5 * radius, 1);
const fontHeight = roundDecimal(scaledRadius * 0.5, 1); const fontHeight = roundDecimal(scaledRadius * 0.5, 1);
const [x, y] = pixelRounded; const [x, y] = pixelRounded;

View File

@ -7,7 +7,6 @@ export function kmToPixels(kilometers, latitude, zoomLevel) {
// Algorithm from: http://wiki.openstreetmap.org/wiki/Zoom_levels // Algorithm from: http://wiki.openstreetmap.org/wiki/Zoom_levels
const latitudeRad = latitude * (Math.PI / 180); const latitudeRad = latitude * (Math.PI / 180);
// Seems like the zoomLevel is off by one // Seems like the zoomLevel is off by one
// eslint-disable-next-line no-restricted-properties, unicorn/prefer-exponentiation-operator
const kmPerPixel = (EARTH_CIRCUMFERENCE_KM * Math.cos(latitudeRad)) / 2 ** (zoomLevel + 9); const kmPerPixel = (EARTH_CIRCUMFERENCE_KM * Math.cos(latitudeRad)) / 2 ** (zoomLevel + 9);
return roundDecimal(kilometers / kmPerPixel, 2); return roundDecimal(kilometers / kmPerPixel, 2);

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
/* eslint-disable no-plusplus, react/no-array-index-key, react/jsx-no-bind */ /* eslint-disable react/no-array-index-key, react/jsx-no-bind */
import dist from 'distributions'; import dist from 'distributions';
import React from 'react'; import React from 'react';
import { Table, Tr, Td, Thead, Th } from 'reactable-arc'; import { Table, Tr, Td, Thead, Th } from 'reactable-arc';

View File

@ -18,7 +18,6 @@
* under the License. * under the License.
*/ */
/* eslint no-param-reassign: [2, {"props": false}] */ /* eslint no-param-reassign: [2, {"props": false}] */
/* eslint-disable no-plusplus */
import d3 from 'd3'; import d3 from 'd3';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { hierarchy } from 'd3-hierarchy'; import { hierarchy } from 'd3-hierarchy';

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
/* eslint no-use-before-define: ["error", { "functions": false }] */ /* eslint no-use-before-define: ["error", { "functions": false }] */
/* eslint-disable no-restricted-syntax, no-plusplus */ /* eslint-disable no-restricted-syntax */
/* eslint-disable react/sort-prop-types */ /* eslint-disable react/sort-prop-types */
import d3 from 'd3'; import d3 from 'd3';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';

View File

@ -17,7 +17,6 @@
* under the License. * under the License.
*/ */
/* eslint-disable no-param-reassign, react/sort-prop-types */ /* eslint-disable no-param-reassign, react/sort-prop-types */
/* eslint-disable no-plusplus */
import d3 from 'd3'; import d3 from 'd3';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { CategoricalColorNamespace } from '@superset-ui/color'; import { CategoricalColorNamespace } from '@superset-ui/color';

View File

@ -1,5 +1,3 @@
/* eslint-disable no-plusplus */
/* /*
Utility function that takes a d3 svg:text selection and a max width, and splits the Utility function that takes a d3 svg:text selection and a max width, and splits the
text's text across multiple tspan lines such that any given line does not exceed max width text's text across multiple tspan lines such that any given line does not exceed max width

View File

@ -17,7 +17,6 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
/* eslint-disable no-plusplus */
import { kebabCase, throttle } from 'lodash'; import { kebabCase, throttle } from 'lodash';
import d3 from 'd3'; import d3 from 'd3';
import nv from 'nvd3'; import nv from 'nvd3';

View File

@ -99,6 +99,7 @@ export default class LineChart extends PureComponent<Props> {
const allSeries = values(groups).map(seriesData => { const allSeries = values(groups).map(seriesData => {
const firstDatum = seriesData[0]; const firstDatum = seriesData[0];
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const key = fieldNames.map(f => firstDatum[f]).join(','); const key = fieldNames.map(f => firstDatum[f]).join(',');
const series: Series = { const series: Series = {
key: key.length === 0 ? channels.y.getTitle() : key, key: key.length === 0 ? channels.y.getTitle() : key,