Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

removed lodash imports and dependencies and wrote js equivalents #9116

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@
"i18next": "^23.16.4",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.2",
"lodash-es": "^4.17.21",
"postcss-loader": "^8.1.1",
"qrcode.react": "^4.1.0",
"raviger": "^4.1.2",
Expand All @@ -107,7 +106,6 @@
"@types/events": "^3.0.3",
"@types/google.maps": "^3.58.1",
"@types/jsdom": "^21.1.7",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.9.0",
"@types/react": "^18.3.12",
"@types/react-copy-to-clipboard": "^5.0.7",
Expand Down
Binary file added src/Utils/.Notifications.js.swp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this file

Binary file not shown.
3 changes: 2 additions & 1 deletion src/Utils/Notifications.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Stack, alert, defaultModules } from "@pnotify/core";
import * as PNotifyMobile from "@pnotify/mobile";
import { camelCase, startCase } from "lodash-es";

import { camelCase, startCase } from "./utils";
Copy link
Member

@sainak sainak Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use absolute imports


defaultModules.set(PNotifyMobile, {});

Expand Down
45 changes: 45 additions & 0 deletions src/Utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useEffect, useRef } from "react";

import { PatientModel } from "@/components/Patient/models";

import { AREACODES, IN_LANDLINE_AREA_CODES } from "@/common/constants";
Expand Down Expand Up @@ -544,3 +546,46 @@ export const fahrenheitToCelsius = (fahrenheit: number) => {
export const keysOf = <T extends object>(obj: T) => {
return Object.keys(obj) as (keyof T)[];
};

// Capitalize the first letter of each word in a string, handling edge cases
export const startCase = (str: string): string => {
if (!str) return "";

return str
.toLowerCase()
.replace(/\s+/g, " ")
.trim()
.split(" ")
.map((word) => (word ? word[0].toUpperCase() + word.slice(1) : ""))
.join(" ");
};
SwanandBhuskute marked this conversation as resolved.
Show resolved Hide resolved

// Converts a string to camelCase format, first word - lowercase and each subsequent word - uppercase letter, with no spaces.
export const camelCase = (str: string) => {
if (!str) return "";
return str
.trim()
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
.replace(/^[A-Z]/, (c) => c.toLowerCase());
};
SwanandBhuskute marked this conversation as resolved.
Show resolved Hide resolved

export const useDebounce = (
callback: (...args: string[]) => void,
delay: number,
) => {
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);

const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const debouncedCallback = (...args: string[]) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callbackRef.current(...args);
}, delay);
};
return debouncedCallback;
};
SwanandBhuskute marked this conversation as resolved.
Show resolved Hide resolved
Comment on lines +572 to +591
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a hook so move it to src/hooks

7 changes: 3 additions & 4 deletions src/components/Common/ExcelFIleDragAndDrop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { forIn } from "lodash-es";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import * as XLSX from "xlsx";
Expand Down Expand Up @@ -68,9 +67,9 @@ export default function ExcelFileDragAndDrop({
const data = XLSX.utils.sheet_to_json(worksheet, { defval: "" });
//converts the date to string
data.forEach((row: any) => {
forIn(row, (value: any, key: string) => {
if (value instanceof Date) {
row[key] = value.toISOString().split("T")[0];
Object.keys(row).forEach((key) => {
if (row[key] instanceof Date) {
row[key] = row[key].toISOString().split("T")[0];
}
});
});
Expand Down
21 changes: 10 additions & 11 deletions src/components/Facility/Investigations/Reports/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import _ from "lodash";
import { useCallback, useReducer, useState } from "react";
import { useTranslation } from "react-i18next";

Expand Down Expand Up @@ -179,16 +178,16 @@ const InvestigationReports = ({ id }: any) => {
),
);

const investigationList = _.chain(data)
.flatMap((i) => i?.data?.results)
.compact()
.flatten()
.map((i) => ({
...i,
name: `${i.name} ${i.groups[0].name && " | " + i.groups[0].name} `,
}))
.unionBy("external_id")
.value();
const investigationList = Array.from(
data
.flatMap((i) => i?.data?.results || [])
.map((i) => ({
...i,
name: `${i.name} ${i.groups[0].name ? " | " + i.groups[0].name : ""}`,
}))
.reduce((map, item) => map.set(item.external_id, item), new Map())
.values(),
);

dispatch({ type: "set_investigations", payload: investigationList });
dispatch({
Expand Down
81 changes: 59 additions & 22 deletions src/components/Facility/Investigations/Reports/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,67 @@
import _ from "lodash";
import { findIndex, memoize } from "lodash-es";

import { InvestigationResponse } from "@/components/Facility/Investigations/Reports/types";

const memoize = <T extends (...args: any[]) => any>(fn: T): T => {
const cache = new Map<string, ReturnType<T>>();
const MAX_CACHE_SIZE = 1000;
return ((...args: Parameters<T>): ReturnType<T> => {
const key = args
.map((arg) =>
typeof arg === "object"
? arg instanceof Date
? arg.getTime().toString()
: JSON.stringify(Object.entries(arg).sort())
: String(arg),
)
.join("|");
if (!cache.has(key)) {
if (cache.size >= MAX_CACHE_SIZE) {
const firstKey: any = cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, fn(...args));
}
return cache.get(key)!;
}) as T;
};

export const transformData = memoize((data: InvestigationResponse) => {
const sessions = _.chain(data)
.map((value: any) => {
return {
...value.session_object,
facility_name: value.consultation_object?.facility_name,
facility_id: value.consultation_object?.facility,
};
})
.uniqBy("session_external_id")
.orderBy("session_created_date", "desc")
.value();
const groupByInvestigation = _.chain(data)
.groupBy("investigation_object.external_id")
.values()
.value();
const sessions = Array.from(
new Map(
data.map((value: any) => [
value.session_object.session_external_id,
{
...value.session_object,
facility_name: value.consultation_object?.facility_name,
facility_id: value.consultation_object?.facility,
},
]),
).values(),
).sort(
(a, b) =>
new Date(b.session_created_date).getTime() -
new Date(a.session_created_date).getTime(),
);

const groupByInvestigation = Object.values(
data.reduce(
(acc, value: any) => {
const key = value.investigation_object.external_id;
if (!acc[key]) acc[key] = [];
acc[key].push(value);
return acc;
},
{} as { [key: string]: any[] },
),
);

const sessionMap = new Map(
sessions.map((session, index) => [session.session_external_id, index]),
);
const reqData = groupByInvestigation.map((value: any) => {
const sessionValues = Array.from({ length: sessions.length });
value.forEach((val: any) => {
const sessionIndex = findIndex(sessions, [
"session_external_id",
val.session_object.session_external_id,
]);
const sessionIndex =
sessionMap.get(val.session_object.session_external_id) ?? -1;
if (sessionIndex > -1) {
sessionValues[sessionIndex] = {
min: val.investigation_object.min_value,
Expand Down Expand Up @@ -58,6 +94,7 @@ export const transformData = memoize((data: InvestigationResponse) => {
sessionValues,
};
});

return { sessions, data: reqData };
});

Expand Down
51 changes: 39 additions & 12 deletions src/components/Facility/Investigations/ShowInvestigation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import _ from "lodash";
import { set } from "lodash-es";
import { useCallback, useReducer } from "react";
import { useTranslation } from "react-i18next";

Expand Down Expand Up @@ -93,7 +91,32 @@ export default function ShowInvestigation(props: ShowInvestigationProps) {

const handleValueChange = (value: any, name: string) => {
const changedFields = { ...state.changedFields };
set(changedFields, name, value);
const keys = name.split(".");
let current = changedFields;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];

// Protect against prototype pollution by skipping unsafe keys - crai
if (key === "__proto__" || key === "constructor" || key === "prototype") {
continue;
}

// Use Object.create(null) to prevent accidental inheritance from Object prototype - coderabbit
current[key] = current[key] || Object.create(null);
current = current[key];
}

const lastKey = keys[keys.length - 1];

// Final key assignment, ensuring no prototype pollution vulnerability - coderabbit
if (
lastKey !== "__proto__" &&
lastKey !== "constructor" &&
lastKey !== "prototype"
) {
current[lastKey] = value;
}

Comment on lines +95 to +118
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract this function to utils and reuse for ./Table component as well

dispatch({ type: "set_changed_fields", changedFields });
};

Expand Down Expand Up @@ -151,15 +174,19 @@ export default function ShowInvestigation(props: ShowInvestigationProps) {
};

const handleUpdateCancel = useCallback(() => {
const changedValues = _.chain(state.initialValues)
.map((val: any, _key: string) => ({
id: val?.id,
initialValue: val?.notes || val?.value || null,
value: val?.value || null,
notes: val?.notes || null,
}))
.reduce((acc: any, cur: any) => ({ ...acc, [cur.id]: cur }), {})
.value();
const changedValues = Object.keys(state.initialValues).reduce(
(acc: any, key: any) => {
const val = state.initialValues[key];
acc[key] = {
id: val?.id,
initialValue: val?.notes || val?.value || null,
value: val?.value || null,
notes: val?.notes || null,
};
return acc;
},
{},
);
dispatch({ type: "set_changed_fields", changedFields: changedValues });
}, [state.initialValues]);

Expand Down
21 changes: 19 additions & 2 deletions src/components/Facility/Investigations/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { set } from "lodash-es";
import { useState } from "react";

import { SelectFormField } from "@/components/Form/FormFields/SelectFormField";
Expand Down Expand Up @@ -60,7 +59,25 @@

const handleValueChange = (value: any, name: string) => {
const form = { ...state };
set(form, name, value);
const keys = name.split(".");

// Validate keys to prevent prototype pollution - coderabbit suggested
if (
keys.some((key) =>
["__proto__", "constructor", "prototype"].includes(key),
)
) {
console.error("Invalid object key detected");
return;
}

let current = form;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!current[key]) current[key] = {};
current = current[key];
}
current[keys[keys.length - 1]] = value;

Check warning

Code scanning / CodeQL

Prototype-polluting function Medium

The property chain
here
is recursively assigned to
current
without guarding against prototype pollution.
dispatch({ type: "set_form", form });
};

Expand Down
Loading
Loading