chore: replace Lodash usage with native JS implementation (#31907)

Signed-off-by: hainenber <dotronghai96@gmail.com>
This commit is contained in:
Đỗ Trọng Hải 2025-01-22 01:33:31 +07:00 committed by GitHub
parent eec374426f
commit f8fe780f52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 45 additions and 54 deletions

View File

@ -18,7 +18,6 @@
* under the License. * under the License.
*/ */
import { JsonObject } from '@superset-ui/core'; import { JsonObject } from '@superset-ui/core';
import { isString } from 'lodash';
export const getTimeOffset = ( export const getTimeOffset = (
series: JsonObject, series: JsonObject,
@ -36,7 +35,9 @@ export const hasTimeOffset = (
series: JsonObject, series: JsonObject,
timeCompare: string[], timeCompare: string[],
): boolean => ): boolean =>
isString(series.name) ? !!getTimeOffset(series, timeCompare) : false; typeof series.name === 'string'
? !!getTimeOffset(series, timeCompare)
: false;
export const getOriginalSeries = ( export const getOriginalSeries = (
seriesName: string, seriesName: string,

View File

@ -19,7 +19,7 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import { mergeWith, isArray } from 'lodash'; import { mergeWith } from 'lodash';
import { FeatureFlag, isFeatureEnabled } from '../utils'; import { FeatureFlag, isFeatureEnabled } from '../utils';
interface SafeMarkdownProps { interface SafeMarkdownProps {
@ -33,7 +33,7 @@ export function getOverrideHtmlSchema(
htmlSchemaOverrides: SafeMarkdownProps['htmlSchemaOverrides'], htmlSchemaOverrides: SafeMarkdownProps['htmlSchemaOverrides'],
) { ) {
return mergeWith(originalSchema, htmlSchemaOverrides, (objValue, srcValue) => return mergeWith(originalSchema, htmlSchemaOverrides, (objValue, srcValue) =>
isArray(objValue) ? objValue.concat(srcValue) : undefined, Array.isArray(objValue) ? objValue.concat(srcValue) : undefined,
); );
} }

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { isEmpty, isBoolean } from 'lodash'; import { isEmpty } from 'lodash';
import { QueryObject } from './types'; import { QueryObject } from './types';
@ -30,7 +30,7 @@ export default function normalizeOrderBy(
Array.isArray(orderbyClause) && Array.isArray(orderbyClause) &&
orderbyClause.length === 2 && orderbyClause.length === 2 &&
!isEmpty(orderbyClause[0]) && !isEmpty(orderbyClause[0]) &&
isBoolean(orderbyClause[1]) typeof orderbyClause[1] === 'boolean'
) { ) {
return queryObject; return queryObject;
} }

View File

@ -25,7 +25,6 @@ import {
QueryFormData, QueryFormData,
SequentialScheme, SequentialScheme,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { isNumber } from 'lodash';
import { hexToRGB } from './utils/colors'; import { hexToRGB } from './utils/colors';
const DEFAULT_NUM_BUCKETS = 10; const DEFAULT_NUM_BUCKETS = 10;
@ -140,7 +139,7 @@ export function getBreakPointColorScaler(
} else { } else {
// interpolate colors linearly // interpolate colors linearly
const linearScaleDomain = extent(features, accessor); const linearScaleDomain = extent(features, accessor);
if (!linearScaleDomain.some(isNumber)) { if (!linearScaleDomain.some(i => typeof i === 'number')) {
scaler = colorScheme.createLinearScale(); scaler = colorScheme.createLinearScale();
} else { } else {
scaler = colorScheme.createLinearScale( scaler = colorScheme.createLinearScale(

View File

@ -16,7 +16,6 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { isNumber } from 'lodash';
import { DataRecord, DTTM_ALIAS, ValueFormatter } from '@superset-ui/core'; import { DataRecord, DTTM_ALIAS, ValueFormatter } from '@superset-ui/core';
import type { OptionName } from 'echarts/types/src/util/types'; import type { OptionName } from 'echarts/types/src/util/types';
import type { TooltipMarker } from 'echarts/types/src/util/format'; import type { TooltipMarker } from 'echarts/types/src/util/format';
@ -64,7 +63,7 @@ export const extractForecastValuesFromTooltipParams = (
const { marker, seriesId, value } = param; const { marker, seriesId, value } = param;
const context = extractForecastSeriesContext(seriesId); const context = extractForecastSeriesContext(seriesId);
const numericValue = isHorizontal ? value[0] : value[1]; const numericValue = isHorizontal ? value[0] : value[1];
if (isNumber(numericValue)) { if (typeof numericValue === 'number') {
if (!(context.name in values)) if (!(context.name in values))
values[context.name] = { values[context.name] = {
marker: marker || '', marker: marker || '',
@ -97,7 +96,7 @@ export const formatForecastTooltipSeries = ({
formatter: ValueFormatter; formatter: ValueFormatter;
}): string[] => { }): string[] => {
const name = `${marker}${sanitizeHtml(seriesName)}`; const name = `${marker}${sanitizeHtml(seriesName)}`;
let value = isNumber(observation) ? formatter(observation) : ''; let value = typeof observation === 'number' ? formatter(observation) : '';
if (forecastTrend || forecastLower || forecastUpper) { if (forecastTrend || forecastLower || forecastUpper) {
// forecast values take the form of "20, y = 30 (10, 40)" // forecast values take the form of "20, y = 30 (10, 40)"
// where the first part is the observation, the second part is the forecast trend // where the first part is the observation, the second part is the forecast trend

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { DataRecord, DataRecordValue } from '@superset-ui/core'; import { DataRecord, DataRecordValue } from '@superset-ui/core';
import { groupBy as _groupBy, isNumber, transform } from 'lodash'; import { groupBy as _groupBy, transform } from 'lodash';
export type TreeNode = { export type TreeNode = {
name: DataRecordValue; name: DataRecordValue;
@ -28,7 +28,7 @@ export type TreeNode = {
}; };
function getMetricValue(datum: DataRecord, metric: string) { function getMetricValue(datum: DataRecord, metric: string) {
return isNumber(datum[metric]) ? (datum[metric] as number) : 0; return typeof datum[metric] === 'number' ? (datum[metric] as number) : 0;
} }
export function treeBuilder( export function treeBuilder(

View File

@ -61,7 +61,7 @@ import {
PlusCircleOutlined, PlusCircleOutlined,
TableOutlined, TableOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { isEmpty, isNumber } from 'lodash'; import { isEmpty } from 'lodash';
import { import {
ColorSchemeEnum, ColorSchemeEnum,
DataColumnMeta, DataColumnMeta,
@ -899,7 +899,9 @@ export default function TableChart<D extends DataRecord = DataRecord>(
/* The following classes are added to support custom CSS styling */ /* The following classes are added to support custom CSS styling */
className={cx( className={cx(
'cell-bar', 'cell-bar',
isNumber(value) && value < 0 ? 'negative' : 'positive', typeof value === 'number' && value < 0
? 'negative'
: 'positive',
)} )}
css={cellBarStyles} css={cellBarStyles}
role="presentation" role="presentation"

View File

@ -50,7 +50,7 @@ import { mountExploreUrl } from 'src/explore/exploreUtils';
import { postFormData } from 'src/explore/exploreUtils/formData'; import { postFormData } from 'src/explore/exploreUtils/formData';
import { URL_PARAMS } from 'src/constants'; import { URL_PARAMS } from 'src/constants';
import { SelectValue } from 'antd/lib/select'; import { SelectValue } from 'antd/lib/select';
import { isEmpty, isString } from 'lodash'; import { isEmpty } from 'lodash';
interface QueryDatabase { interface QueryDatabase {
id?: number; id?: number;
@ -280,7 +280,7 @@ export const SaveDatasetModal = ({
// Remove the special filters entry from the templateParams // Remove the special filters entry from the templateParams
// before saving the dataset. // before saving the dataset.
let templateParams; let templateParams;
if (isString(datasource?.templateParams)) { if (typeof datasource?.templateParams === 'string') {
const p = JSON.parse(datasource.templateParams); const p = JSON.parse(datasource.templateParams);
/* eslint-disable-next-line no-underscore-dangle */ /* eslint-disable-next-line no-underscore-dangle */
if (p._filters) { if (p._filters) {

View File

@ -50,7 +50,7 @@ import type {
CursorPosition, CursorPosition,
} from 'src/SqlLab/types'; } from 'src/SqlLab/types';
import type { DatabaseObject } from 'src/features/databases/types'; import type { DatabaseObject } from 'src/features/databases/types';
import { debounce, throttle, isBoolean, isEmpty } from 'lodash'; import { debounce, throttle, isEmpty } from 'lodash';
import Modal from 'src/components/Modal'; import Modal from 'src/components/Modal';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import Button from 'src/components/Button'; import Button from 'src/components/Button';
@ -281,9 +281,10 @@ const SqlEditor: FC<Props> = ({
if (unsavedQueryEditor?.id === queryEditor.id) { if (unsavedQueryEditor?.id === queryEditor.id) {
dbId = unsavedQueryEditor.dbId || dbId; dbId = unsavedQueryEditor.dbId || dbId;
latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId; latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId;
hideLeftBar = isBoolean(unsavedQueryEditor.hideLeftBar) hideLeftBar =
? unsavedQueryEditor.hideLeftBar typeof unsavedQueryEditor.hideLeftBar === 'boolean'
: hideLeftBar; ? unsavedQueryEditor.hideLeftBar
: hideLeftBar;
} }
return { return {
hasSqlStatement: Boolean(queryEditor.sql?.trim().length > 0), hasSqlStatement: Boolean(queryEditor.sql?.trim().length > 0),

View File

@ -19,7 +19,7 @@
import { useState } from 'react'; import { useState } from 'react';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import { omit, isUndefined, omitBy } from 'lodash'; import { omit, omitBy } from 'lodash';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { waitFor, within } from '@testing-library/react'; import { waitFor, within } from '@testing-library/react';
import { render, screen } from 'spec/helpers/testing-library'; import { render, screen } from 'spec/helpers/testing-library';
@ -166,7 +166,7 @@ test('should generate Explore url', async () => {
form_data: { form_data: {
...omitBy( ...omitBy(
omit(formData, ['slice_id', 'slice_name', 'dashboards']), omit(formData, ['slice_id', 'slice_name', 'dashboards']),
isUndefined, i => i === undefined,
), ),
groupby: ['name'], groupby: ['name'],
adhoc_filters: [ adhoc_filters: [

View File

@ -30,7 +30,6 @@ import { ControlConfig } from '@superset-ui/chart-controls';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { FixedSizeList as List } from 'react-window'; import { FixedSizeList as List } from 'react-window';
import { isArray } from 'lodash';
import { matchSorter, rankings } from 'match-sorter'; import { matchSorter, rankings } from 'match-sorter';
import Alert from 'src/components/Alert'; import Alert from 'src/components/Alert';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal'; import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
@ -142,7 +141,7 @@ export default function DataSourcePanel({
const allowedColumns = useMemo(() => { const allowedColumns = useMemo(() => {
const validators = Object.values(dropzones); const validators = Object.values(dropzones);
if (!isArray(_columns)) return []; if (!Array.isArray(_columns)) return [];
return _columns.filter(column => return _columns.filter(column =>
validators.some(validator => validators.some(validator =>
validator({ validator({

View File

@ -19,7 +19,7 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { InputNumber } from 'src/components/Input'; import { InputNumber } from 'src/components/Input';
import { t, styled } from '@superset-ui/core'; import { t, styled } from '@superset-ui/core';
import { debounce, parseInt } from 'lodash'; import { debounce } from 'lodash';
import ControlHeader from 'src/explore/components/ControlHeader'; import ControlHeader from 'src/explore/components/ControlHeader';
type ValueType = (number | null)[]; type ValueType = (number | null)[];
@ -47,7 +47,7 @@ const parseNumber = (value: undefined | number | string | null) => {
if ( if (
value === null || value === null ||
value === undefined || value === undefined ||
(typeof value === 'string' && Number.isNaN(parseInt(value))) (typeof value === 'string' && Number.isNaN(Number.parseInt(value, 10)))
) { ) {
return null; return null;
} }

View File

@ -30,7 +30,7 @@ import {
CategoricalColorNamespace, CategoricalColorNamespace,
} from '@superset-ui/core'; } from '@superset-ui/core';
import AntdSelect from 'antd/lib/select'; import AntdSelect from 'antd/lib/select';
import { isFunction, sortBy } from 'lodash'; import { sortBy } from 'lodash';
import ControlHeader from 'src/explore/components/ControlHeader'; import ControlHeader from 'src/explore/components/ControlHeader';
import { Tooltip } from 'src/components/Tooltip'; import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
@ -163,7 +163,7 @@ const ColorSchemeControl = ({
} }
let result = value || defaultScheme; let result = value || defaultScheme;
if (result === 'SUPERSET_DEFAULT') { if (result === 'SUPERSET_DEFAULT') {
const schemesObject = isFunction(schemes) ? schemes() : schemes; const schemesObject = typeof schemes === 'function' ? schemes() : schemes;
result = schemesObject?.SUPERSET_DEFAULT?.id; result = schemesObject?.SUPERSET_DEFAULT?.id;
} }
return result; return result;
@ -179,8 +179,8 @@ const ColorSchemeControl = ({
</Option>, </Option>,
]; ];
} }
const schemesObject = isFunction(schemes) ? schemes() : schemes; const schemesObject = typeof schemes === 'function' ? schemes() : schemes;
const controlChoices = isFunction(choices) ? choices() : choices; const controlChoices = typeof choices === 'function' ? choices() : choices;
const allColorOptions: string[] = []; const allColorOptions: string[] = [];
const filteredColorOptions = controlChoices.filter(o => { const filteredColorOptions = controlChoices.filter(o => {
const option = o[0]; const option = o[0];

View File

@ -51,7 +51,6 @@ import ViewQueryModalFooter from 'src/explore/components/controls/ViewQueryModal
import ViewQuery from 'src/explore/components/controls/ViewQuery'; import ViewQuery from 'src/explore/components/controls/ViewQuery';
import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal'; import { SaveDatasetModal } from 'src/SqlLab/components/SaveDatasetModal';
import { safeStringify } from 'src/utils/safeStringify'; import { safeStringify } from 'src/utils/safeStringify';
import { isString } from 'lodash';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const propTypes = { const propTypes = {
@ -383,7 +382,7 @@ class DatasourceControl extends PureComponent {
let extra; let extra;
if (datasource?.extra) { if (datasource?.extra) {
if (isString(datasource.extra)) { if (typeof datasource.extra === 'string') {
try { try {
extra = JSON.parse(datasource.extra); extra = JSON.parse(datasource.extra);
} catch {} // eslint-disable-line no-empty } catch {} // eslint-disable-line no-empty

View File

@ -16,7 +16,6 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { isInteger } from 'lodash';
import { t, customTimeRangeDecode } from '@superset-ui/core'; import { t, customTimeRangeDecode } from '@superset-ui/core';
import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
import { Col, Row } from 'src/components'; import { Col, Row } from 'src/components';
@ -76,7 +75,7 @@ export function CustomFrame(props: FrameComponentProps) {
value: string | number, value: string | number,
) { ) {
// only positive values in grainValue controls // only positive values in grainValue controls
if (isInteger(value) && value > 0) { if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
props.onChange( props.onChange(
customTimeRangeEncode({ customTimeRangeEncode({
...customRange, ...customRange,

View File

@ -24,7 +24,6 @@ import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react'; import { waitFor } from '@testing-library/react';
import { UploadFile } from 'antd/lib/upload/interface'; import { UploadFile } from 'antd/lib/upload/interface';
import { forEach } from 'lodash';
fetchMock.post('glob:*api/v1/database/1/csv_upload/', {}); fetchMock.post('glob:*api/v1/database/1/csv_upload/', {});
fetchMock.post('glob:*api/v1/database/1/excel_upload/', {}); fetchMock.post('glob:*api/v1/database/1/excel_upload/', {});
@ -782,7 +781,7 @@ test('Columnar, form post', async () => {
test('CSV, validate file extension returns false', () => { test('CSV, validate file extension returns false', () => {
const invalidFileNames = ['out', 'out.exe', 'out.csv.exe', '.csv', 'out.xls']; const invalidFileNames = ['out', 'out.exe', 'out.csv.exe', '.csv', 'out.xls'];
forEach(invalidFileNames, fileName => { invalidFileNames.forEach(fileName => {
const file: UploadFile<any> = { const file: UploadFile<any> = {
name: fileName, name: fileName,
uid: 'xp', uid: 'xp',
@ -795,7 +794,7 @@ test('CSV, validate file extension returns false', () => {
test('Excel, validate file extension returns false', () => { test('Excel, validate file extension returns false', () => {
const invalidFileNames = ['out', 'out.exe', 'out.xls.exe', '.csv', 'out.csv']; const invalidFileNames = ['out', 'out.exe', 'out.xls.exe', '.csv', 'out.csv'];
forEach(invalidFileNames, fileName => { invalidFileNames.forEach(fileName => {
const file: UploadFile<any> = { const file: UploadFile<any> = {
name: fileName, name: fileName,
uid: 'xp', uid: 'xp',
@ -814,7 +813,7 @@ test('Columnar, validate file extension returns false', () => {
'.parquet', '.parquet',
'out.excel', 'out.excel',
]; ];
forEach(invalidFileNames, fileName => { invalidFileNames.forEach(fileName => {
const file: UploadFile<any> = { const file: UploadFile<any> = {
name: fileName, name: fileName,
uid: 'xp', uid: 'xp',
@ -827,7 +826,7 @@ test('Columnar, validate file extension returns false', () => {
test('CSV, validate file extension returns true', () => { test('CSV, validate file extension returns true', () => {
const invalidFileNames = ['out.csv', 'out.tsv', 'out.exe.csv', 'out a.csv']; const invalidFileNames = ['out.csv', 'out.tsv', 'out.exe.csv', 'out a.csv'];
forEach(invalidFileNames, fileName => { invalidFileNames.forEach(fileName => {
const file: UploadFile<any> = { const file: UploadFile<any> = {
name: fileName, name: fileName,
uid: 'xp', uid: 'xp',
@ -840,7 +839,7 @@ test('CSV, validate file extension returns true', () => {
test('Excel, validate file extension returns true', () => { test('Excel, validate file extension returns true', () => {
const invalidFileNames = ['out.xls', 'out.xlsx', 'out.exe.xls', 'out a.xls']; const invalidFileNames = ['out.xls', 'out.xlsx', 'out.exe.xls', 'out a.xls'];
forEach(invalidFileNames, fileName => { invalidFileNames.forEach(fileName => {
const file: UploadFile<any> = { const file: UploadFile<any> = {
name: fileName, name: fileName,
uid: 'xp', uid: 'xp',
@ -858,7 +857,7 @@ test('Columnar, validate file extension returns true', () => {
'out.exe.zip', 'out.exe.zip',
'out a.parquet', 'out a.parquet',
]; ];
forEach(invalidFileNames, fileName => { invalidFileNames.forEach(fileName => {
const file: UploadFile<any> = { const file: UploadFile<any> = {
name: fileName, name: fileName,
uid: 'xp', uid: 'xp',

View File

@ -19,14 +19,7 @@
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { compose } from 'redux'; import { compose } from 'redux';
import persistState, { StorageAdapter } from 'redux-localstorage'; import persistState, { StorageAdapter } from 'redux-localstorage';
import { import { isEqual, omitBy, omit, isEqualWith } from 'lodash';
isEqual,
omitBy,
omit,
isUndefined,
isNull,
isEqualWith,
} from 'lodash';
import { ensureIsArray } from '@superset-ui/core'; import { ensureIsArray } from '@superset-ui/core';
export function addToObject( export function addToObject(
@ -195,12 +188,12 @@ export function areObjectsEqual(
let comp1 = obj1; let comp1 = obj1;
let comp2 = obj2; let comp2 = obj2;
if (opts.ignoreUndefined) { if (opts.ignoreUndefined) {
comp1 = omitBy(comp1, isUndefined); comp1 = omitBy(comp1, i => i === undefined);
comp2 = omitBy(comp2, isUndefined); comp2 = omitBy(comp2, i => i === undefined);
} }
if (opts.ignoreNull) { if (opts.ignoreNull) {
comp1 = omitBy(comp1, isNull); comp1 = omitBy(comp1, i => i === null);
comp2 = omitBy(comp2, isNull); comp2 = omitBy(comp2, i => i === null);
} }
if (opts.ignoreFields?.length) { if (opts.ignoreFields?.length) {
const ignoreFields = ensureIsArray(opts.ignoreFields); const ignoreFields = ensureIsArray(opts.ignoreFields);