From 94f9ce6ae8f4bf99cbb08c95d828a42f89c55dbc Mon Sep 17 00:00:00 2001
From: Mgrdich <46796009+Mgrdich@users.noreply.github.com>
Date: Wed, 17 Apr 2024 12:35:28 +0400
Subject: [PATCH] FE: Remove lodash lib (#301)
---
frontend/.eslintrc.json | 4 +-
frontend/package.json | 2 -
frontend/pnpm-lock.yaml | 10 -----
frontend/src/components/App.tsx | 2 +-
.../Actions/__tests__/Actions.spec.tsx | 25 +++++++----
frontend/src/components/Connect/New/New.tsx | 3 +-
.../ConsumerGroups/Details/Details.tsx | 7 ++-
.../Topics/Topic/Messages/Filters/utils.ts | 2 +-
.../Topics/Topic/SendMessage/utils.ts | 5 ++-
.../utils/__test__/updateSortingState.spec.ts | 2 +-
.../lib/functions/__tests__/compact.spec.ts | 34 ++++++++++++++
.../lib/functions/__tests__/groupBy.spec.ts | 45 +++++++++++++++++++
frontend/src/lib/functions/compact.ts | 13 ++++++
frontend/src/lib/functions/groupBy.ts | 22 +++++++++
frontend/src/lib/functions/keyBy.ts | 1 -
frontend/src/lib/hooks/api/kafkaConnect.ts | 27 +++++++++--
.../src/widgets/ClusterConfigForm/schema.ts | 3 +-
.../ClusterConfigForm/utils/getJaasConfig.ts | 4 +-
18 files changed, 173 insertions(+), 38 deletions(-)
create mode 100644 frontend/src/lib/functions/__tests__/compact.spec.ts
create mode 100644 frontend/src/lib/functions/__tests__/groupBy.spec.ts
create mode 100644 frontend/src/lib/functions/compact.ts
create mode 100644 frontend/src/lib/functions/groupBy.ts
diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
index ae5968839..a2f7fd806 100644
--- a/frontend/.eslintrc.json
+++ b/frontend/.eslintrc.json
@@ -75,7 +75,9 @@
{
"props": true,
"ignorePropertyModificationsFor": [
- "state"
+ "state",
+ "acc",
+ "accumulator"
]
}
],
diff --git a/frontend/package.json b/frontend/package.json
index 61cb4e7be..cb0ce7690 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,7 +17,6 @@
"classnames": "^2.2.6",
"json-schema-faker": "^0.5.6",
"jsonpath-plus": "^7.2.0",
- "lodash": "^4.17.21",
"lossless-json": "^2.0.8",
"pretty-ms": "7.0.1",
"react": "^18.1.0",
@@ -62,7 +61,6 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/user-event": "^14.4.3",
"@types/eventsource": "^1.1.8",
- "@types/lodash": "^4.14.172",
"@types/lossless-json": "^1.0.1",
"@types/node": "^20.11.17",
"@types/react": "^18.0.9",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 3bd4445b1..b074cb11e 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -58,9 +58,6 @@ dependencies:
jsonpath-plus:
specifier: ^7.2.0
version: 7.2.0
- lodash:
- specifier: ^4.17.21
- version: 4.17.21
lossless-json:
specifier: ^2.0.8
version: 2.0.11
@@ -141,9 +138,6 @@ devDependencies:
'@types/eventsource':
specifier: ^1.1.8
version: 1.1.11
- '@types/lodash':
- specifier: ^4.14.172
- version: 4.14.177
'@types/lossless-json':
specifier: ^1.0.1
version: 1.0.2
@@ -1896,10 +1890,6 @@ packages:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true
- /@types/lodash@4.14.177:
- resolution: {integrity: sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==}
- dev: true
-
/@types/lossless-json@1.0.2:
resolution: {integrity: sha512-RdV8M8qlWUpmk7gnY3fBB4TNn3Ab8hMMqhJC/sG77t8Zk+hjVwvZGTFv+upEBUkxXbq0+UxGAPhOml83w1IkIQ==}
dev: true
diff --git a/frontend/src/components/App.tsx b/frontend/src/components/App.tsx
index a2e1b1c72..16dd1305d 100644
--- a/frontend/src/components/App.tsx
+++ b/frontend/src/components/App.tsx
@@ -97,7 +97,7 @@ const App: React.FC = () => {
-
+
);
};
diff --git a/frontend/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx b/frontend/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx
index 9dce7507f..27743949b 100644
--- a/frontend/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx
+++ b/frontend/src/components/Connect/Details/Actions/__tests__/Actions.spec.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { render, WithRoute } from 'lib/testHelpers';
import { clusterConnectConnectorPath } from 'lib/paths';
import Actions from 'components/Connect/Details/Actions/Actions';
-import { ConnectorAction, ConnectorState } from 'generated-sources';
+import { Connector, ConnectorAction, ConnectorState } from 'generated-sources';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
@@ -10,7 +10,16 @@ import {
useUpdateConnectorState,
} from 'lib/hooks/api/kafkaConnect';
import { connector } from 'lib/fixtures/kafkaConnect';
-import set from 'lodash/set';
+
+function setConnectorStatus(con: Connector, state: ConnectorState) {
+ return {
+ ...con,
+ status: {
+ ...con,
+ state,
+ },
+ };
+}
const mockHistoryPush = jest.fn();
const deleteConnector = jest.fn();
@@ -66,7 +75,7 @@ describe('Actions', () => {
it('renders buttons when paused', async () => {
(useConnector as jest.Mock).mockImplementation(() => ({
- data: set({ ...connector }, 'status.state', ConnectorState.PAUSED),
+ data: setConnectorStatus(connector, ConnectorState.PAUSED),
}));
renderComponent();
await afterClickRestartButton();
@@ -78,7 +87,7 @@ describe('Actions', () => {
it('renders buttons when failed', async () => {
(useConnector as jest.Mock).mockImplementation(() => ({
- data: set({ ...connector }, 'status.state', ConnectorState.FAILED),
+ data: setConnectorStatus(connector, ConnectorState.FAILED),
}));
renderComponent();
await afterClickRestartButton();
@@ -90,7 +99,7 @@ describe('Actions', () => {
it('renders buttons when unassigned', async () => {
(useConnector as jest.Mock).mockImplementation(() => ({
- data: set({ ...connector }, 'status.state', ConnectorState.UNASSIGNED),
+ data: setConnectorStatus(connector, ConnectorState.UNASSIGNED),
}));
renderComponent();
await afterClickRestartButton();
@@ -102,7 +111,7 @@ describe('Actions', () => {
it('renders buttons when running connector action', async () => {
(useConnector as jest.Mock).mockImplementation(() => ({
- data: set({ ...connector }, 'status.state', ConnectorState.RUNNING),
+ data: setConnectorStatus(connector, ConnectorState.RUNNING),
}));
renderComponent();
await afterClickRestartButton();
@@ -115,7 +124,7 @@ describe('Actions', () => {
describe('mutations', () => {
beforeEach(() => {
(useConnector as jest.Mock).mockImplementation(() => ({
- data: set({ ...connector }, 'status.state', ConnectorState.RUNNING),
+ data: setConnectorStatus(connector, ConnectorState.RUNNING),
}));
});
@@ -185,7 +194,7 @@ describe('Actions', () => {
it('calls resumeConnector when resume button clicked', async () => {
const resumeConnector = jest.fn();
(useConnector as jest.Mock).mockImplementation(() => ({
- data: set({ ...connector }, 'status.state', ConnectorState.PAUSED),
+ data: setConnectorStatus(connector, ConnectorState.PAUSED),
}));
(useUpdateConnectorState as jest.Mock).mockImplementation(() => ({
mutateAsync: resumeConnector,
diff --git a/frontend/src/components/Connect/New/New.tsx b/frontend/src/components/Connect/New/New.tsx
index bf285838d..db1d82e3c 100644
--- a/frontend/src/components/Connect/New/New.tsx
+++ b/frontend/src/components/Connect/New/New.tsx
@@ -18,7 +18,6 @@ import { Button } from 'components/common/Button/Button';
import PageHeading from 'components/common/PageHeading/PageHeading';
import Heading from 'components/common/heading/Heading.styled';
import { useConnects, useCreateConnector } from 'lib/hooks/api/kafkaConnect';
-import get from 'lodash/get';
import { Connect } from 'generated-sources';
import * as S from './New.styled';
@@ -45,7 +44,7 @@ const New: React.FC = () => {
mode: 'all',
resolver: yupResolver(validationSchema),
defaultValues: {
- connectName: get(connects, '0.name', ''),
+ connectName: connects.length > 0 ? connects[0].name : '',
name: '',
config: '',
},
diff --git a/frontend/src/components/ConsumerGroups/Details/Details.tsx b/frontend/src/components/ConsumerGroups/Details/Details.tsx
index f5cae44df..e7f952c8b 100644
--- a/frontend/src/components/ConsumerGroups/Details/Details.tsx
+++ b/frontend/src/components/ConsumerGroups/Details/Details.tsx
@@ -11,7 +11,7 @@ import ClusterContext from 'components/contexts/ClusterContext';
import PageHeading from 'components/common/PageHeading/PageHeading';
import * as Metrics from 'components/common/Metrics';
import { Tag } from 'components/common/Tag/Tag.styled';
-import groupBy from 'lodash/groupBy';
+import groupBy from 'lib/functions/groupBy';
import { Table } from 'components/common/table/Table/Table.styled';
import getTagColor from 'components/common/Tag/getTagColor';
import { Dropdown } from 'components/common/Dropdown';
@@ -48,7 +48,10 @@ const Details: React.FC = () => {
navigate(clusterConsumerGroupResetRelativePath);
};
- const partitionsByTopic = groupBy(consumerGroup.data?.partitions, 'topic');
+ const partitionsByTopic = groupBy(
+ consumerGroup.data?.partitions || [],
+ 'topic'
+ );
const filteredPartitionsByTopic = Object.keys(partitionsByTopic).filter(
(el) => el.includes(searchValue)
);
diff --git a/frontend/src/components/Topics/Topic/Messages/Filters/utils.ts b/frontend/src/components/Topics/Topic/Messages/Filters/utils.ts
index 585541373..43efe5b20 100644
--- a/frontend/src/components/Topics/Topic/Messages/Filters/utils.ts
+++ b/frontend/src/components/Topics/Topic/Messages/Filters/utils.ts
@@ -1,6 +1,6 @@
import { Partition, PollingMode, SeekType } from 'generated-sources';
-import compact from 'lodash/compact';
import { Option } from 'react-multi-select-component';
+import compact from 'lib/functions/compact';
export function isModeOptionWithInput(value: PollingMode) {
return (
diff --git a/frontend/src/components/Topics/Topic/SendMessage/utils.ts b/frontend/src/components/Topics/Topic/SendMessage/utils.ts
index 46d9e1278..ddc3c8d68 100644
--- a/frontend/src/components/Topics/Topic/SendMessage/utils.ts
+++ b/frontend/src/components/Topics/Topic/SendMessage/utils.ts
@@ -6,7 +6,6 @@ import {
import jsf from 'json-schema-faker';
import Ajv, { DefinedError } from 'ajv/dist/2020';
import addFormats from 'ajv-formats';
-import upperFirst from 'lodash/upperFirst';
jsf.option('fillProperties', false);
jsf.option('alwaysFakeOptionals', true);
@@ -53,6 +52,10 @@ export const getSerdeOptions = (items: SerdeDescription[]) => {
}, []);
};
+function upperFirst(str: string) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
+
export const validateBySchema = (
value: string,
schema: string | undefined,
diff --git a/frontend/src/components/common/NewTable/utils/__test__/updateSortingState.spec.ts b/frontend/src/components/common/NewTable/utils/__test__/updateSortingState.spec.ts
index 07bd60991..4abcaa071 100644
--- a/frontend/src/components/common/NewTable/utils/__test__/updateSortingState.spec.ts
+++ b/frontend/src/components/common/NewTable/utils/__test__/updateSortingState.spec.ts
@@ -1,6 +1,6 @@
import updateSortingState from 'components/common/NewTable/utils/updateSortingState';
import { SortingState } from '@tanstack/react-table';
-import compact from 'lodash/compact';
+import compact from 'lib/functions/compact';
const updater = (previousState: SortingState): SortingState => {
return compact(
diff --git a/frontend/src/lib/functions/__tests__/compact.spec.ts b/frontend/src/lib/functions/__tests__/compact.spec.ts
new file mode 100644
index 000000000..9871454e8
--- /dev/null
+++ b/frontend/src/lib/functions/__tests__/compact.spec.ts
@@ -0,0 +1,34 @@
+import compact from 'lib/functions/compact';
+
+describe('compact', () => {
+ it('should remove falsey values from the array', () => {
+ const input = [0, 1, false, 2, '', 3, null, undefined, 4, NaN, 5];
+ const expected = [1, 2, 3, 4, 5];
+ expect(compact(input)).toEqual(expected);
+ });
+
+ it('should return an empty array if all values are falsey', () => {
+ const input = [0, false, '', null, undefined, NaN];
+ const expected: number[] = [];
+ expect(compact(input)).toEqual(expected);
+ });
+
+ it('should return a new array with only truthy values preserved', () => {
+ const input = [1, 'hello', true, [], { a: 1 }, 42];
+ const expected = [1, 'hello', true, [], { a: 1 }, 42];
+ expect(compact(input)).toEqual(expected);
+ });
+
+ it('should preserve non-falsey values in their original order', () => {
+ const input = [1, null, 2, undefined, 3, false, 4];
+ const expected = [1, 2, 3, 4];
+ expect(compact(input)).toEqual(expected);
+ });
+
+ it('should not modify the original array', () => {
+ const input = [0, 1, 2, false, '', null, undefined, NaN];
+ const inputCopy = [...input];
+ compact(input);
+ expect(input).toEqual(inputCopy);
+ });
+});
diff --git a/frontend/src/lib/functions/__tests__/groupBy.spec.ts b/frontend/src/lib/functions/__tests__/groupBy.spec.ts
new file mode 100644
index 000000000..4c2433453
--- /dev/null
+++ b/frontend/src/lib/functions/__tests__/groupBy.spec.ts
@@ -0,0 +1,45 @@
+import groupBy from 'lib/functions/groupBy';
+
+describe('groupBy', () => {
+ it('should group objects in the array by the specified key', () => {
+ const input = [
+ { id: 1, name: 'John' },
+ { id: 2, name: 'Jane' },
+ { id: 3, name: 'Doe' },
+ { id: 4, name: 'John' },
+ ];
+
+ const result = groupBy(input, 'name');
+
+ expect(result).toEqual({
+ John: [
+ { id: 1, name: 'John' },
+ { id: 4, name: 'John' },
+ ],
+ Jane: [{ id: 2, name: 'Jane' }],
+ Doe: [{ id: 3, name: 'Doe' }],
+ });
+ });
+
+ it('should return an empty object when the input array is empty', () => {
+ const result = groupBy([], 'name');
+
+ expect(result).toEqual({});
+ });
+
+ it('should handle objects with undefined values for the specified key', () => {
+ const input = [
+ { id: 1, name: 'John' },
+ { id: 2, name: undefined },
+ { id: 3, name: 'Doe' },
+ { id: 4 },
+ ];
+
+ const result = groupBy(input, 'name');
+
+ expect(result).toEqual({
+ John: [{ id: 1, name: 'John' }],
+ Doe: [{ id: 3, name: 'Doe' }],
+ });
+ });
+});
diff --git a/frontend/src/lib/functions/compact.ts b/frontend/src/lib/functions/compact.ts
new file mode 100644
index 000000000..dad8315f2
--- /dev/null
+++ b/frontend/src/lib/functions/compact.ts
@@ -0,0 +1,13 @@
+/**
+ * @description
+ * Creates an array with all falsey values removed. The values false, null, 0, "", undefined, and NaN are
+ * falsey.
+ *
+ * @param array The array to compact.
+ * @return Returns the new array of filtered values.
+ */
+export default function compact(
+ array: Array
+): T[] {
+ return array.filter(Boolean) as T[];
+}
diff --git a/frontend/src/lib/functions/groupBy.ts b/frontend/src/lib/functions/groupBy.ts
new file mode 100644
index 000000000..df2f5d149
--- /dev/null
+++ b/frontend/src/lib/functions/groupBy.ts
@@ -0,0 +1,22 @@
+export default function groupBy(
+ collections: T[],
+ key: string
+) {
+ return collections.reduce>((acc, curr) => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ const groupByKey = key in curr ? curr[key] : null;
+
+ if (typeof groupByKey !== 'string' && typeof groupByKey !== 'number') {
+ return acc;
+ }
+
+ if (acc[groupByKey]) {
+ acc[groupByKey].push(curr);
+ return acc;
+ }
+
+ acc[groupByKey] = [curr];
+ return acc;
+ }, {});
+}
diff --git a/frontend/src/lib/functions/keyBy.ts b/frontend/src/lib/functions/keyBy.ts
index 8a5b84ed0..76a8f2f5a 100644
--- a/frontend/src/lib/functions/keyBy.ts
+++ b/frontend/src/lib/functions/keyBy.ts
@@ -13,7 +13,6 @@ export function keyBy>(
return collection.reduce>((acc, cur) => {
const key = cur[property] as unknown as PropertyKey;
- // eslint-disable-next-line no-param-reassign
acc[key] = cur;
return acc;
diff --git a/frontend/src/lib/hooks/api/kafkaConnect.ts b/frontend/src/lib/hooks/api/kafkaConnect.ts
index c17b381e4..225e72165 100644
--- a/frontend/src/lib/hooks/api/kafkaConnect.ts
+++ b/frontend/src/lib/hooks/api/kafkaConnect.ts
@@ -5,7 +5,6 @@ import {
NewConnector,
} from 'generated-sources';
import { kafkaConnectApiClient as api } from 'lib/api';
-import sortBy from 'lodash/sortBy';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { ClusterName } from 'lib/interfaces/cluster';
import { showSuccessAlert } from 'lib/errorHandling';
@@ -55,7 +54,16 @@ export function useConnectors(clusterName: ClusterName, search?: string) {
connectorsKey(clusterName, search),
() => api.getAllConnectors({ clusterName, search }),
{
- select: (data) => sortBy(data, 'name'),
+ select: (data) =>
+ [...data].sort((a, b) => {
+ if (a.name < b.name) {
+ return -1;
+ }
+ if (a.name > b.name) {
+ return 1;
+ }
+ return 0;
+ }),
}
);
}
@@ -67,7 +75,20 @@ export function useConnectorTasks(props: UseConnectorProps) {
connectorTasksKey(props),
() => api.getConnectorTasks(props),
{
- select: (data) => sortBy(data, 'status.id'),
+ select: (data) =>
+ [...data].sort((a, b) => {
+ const aid = a.status.id;
+ const bid = b.status.id;
+
+ if (aid < bid) {
+ return -1;
+ }
+
+ if (aid > bid) {
+ return 1;
+ }
+ return 0;
+ }),
}
);
}
diff --git a/frontend/src/widgets/ClusterConfigForm/schema.ts b/frontend/src/widgets/ClusterConfigForm/schema.ts
index abd17f34b..68ffaa743 100644
--- a/frontend/src/widgets/ClusterConfigForm/schema.ts
+++ b/frontend/src/widgets/ClusterConfigForm/schema.ts
@@ -1,4 +1,3 @@
-import { isArray } from 'lodash';
import { object, string, number, array, boolean, mixed, lazy } from 'yup';
const requiredString = string().required('required field');
@@ -61,7 +60,7 @@ const kafkaConnectSchema = object({
});
const kafkaConnectsSchema = lazy((value) => {
- if (isArray(value)) {
+ if (Array.isArray(value)) {
return array().of(kafkaConnectSchema);
}
return mixed().optional();
diff --git a/frontend/src/widgets/ClusterConfigForm/utils/getJaasConfig.ts b/frontend/src/widgets/ClusterConfigForm/utils/getJaasConfig.ts
index 23578159d..006f401fa 100644
--- a/frontend/src/widgets/ClusterConfigForm/utils/getJaasConfig.ts
+++ b/frontend/src/widgets/ClusterConfigForm/utils/getJaasConfig.ts
@@ -1,5 +1,3 @@
-import { isUndefined } from 'lodash';
-
const JAAS_CONFIGS = {
'SASL/GSSAPI': 'com.sun.security.auth.module.Krb5LoginModule',
'SASL/OAUTHBEARER':
@@ -21,7 +19,7 @@ export const getJaasConfig = (
) => {
const optionsString = Object.entries(options)
.map(([key, value]) => {
- if (isUndefined(value)) return null;
+ if (value === undefined) return null;
if (value === 'true' || value === 'false') {
return ` ${key}=${value}`;
}