Skip to content

Commit

Permalink
Merge pull request #1224 from City-of-Helsinki/remove-individual-lodash
Browse files Browse the repository at this point in the history
Remove individual lodash functions add patch workflow
  • Loading branch information
mrTuomoK authored Feb 23, 2024
2 parents 52a3bd8 + abb4bba commit 7b71112
Show file tree
Hide file tree
Showing 24 changed files with 94 additions and 226 deletions.
47 changes: 47 additions & 0 deletions .github/workflows/patchrelease.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: patchrelease

on:
push:
branches:
- patchrelease

jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Read .nvmrc
run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_OUTPUT
id: nvmrc

- name: setup node ${{ steps.nvmrc.outputs.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: '${{ steps.nvmrc.outputs.NODE_VERSION }}'
registry-url: 'https://registry.npmjs.org'

- name: install dependencies
run: yarn

- name: build design tokens package
run: yarn build
working-directory: ./packages/design-tokens

- name: build core package
run: yarn build
working-directory: ./packages/core

- name: build react package
run: yarn build
working-directory: ./packages/react

- name: build hds-js package
run: yarn build:hds-js
working-directory: ./packages/react

- name: release npm packages
run: yarn release --dist-tag patch
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
15 changes: 9 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.X.X] - Month, XX, 202X

### React
Expand All @@ -24,7 +19,7 @@ Changes that are not related to specific components

#### Fixed

- [Component] What bugs/typos are fixed?
- Removed old & deprecated individual `lodash` dependencies and replaced with the full package and importing the needed functions only.

### Core

Expand Down Expand Up @@ -603,6 +598,14 @@ There is a brand new Figma library available. The licenses are checked and provi

- [Header] Add HDS Header.sketch file to shared libraries for HDS 3.0.0 Alpha release purposes. The shared library file includes new Header and Side navigation symbols.

## [2.17.1] - Feb, 7, 2024

### React

#### Fixed

- Removed old & deprecated individual `lodash` dependencies and replaced with the full package and importing the needed functions only.

## [2.17.0] - Aug, 18, 2023

### React
Expand Down
4 changes: 1 addition & 3 deletions packages/hds-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
"cookie": "^0.4.1",
"http-status-typed": "^1.0.1",
"jwt-decode": "^3.1.2",
"lodash.isobject": "3.0.2",
"lodash.isundefined": "3.0.1",
"lodash.pick": "^4.4.0",
"lodash": "^4.17.21",
"oidc-client-ts": "^2.2.2"
},
"scripts": {
Expand Down
12 changes: 1 addition & 11 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,17 +135,7 @@
"http-status-typed": "^1.0.1",
"jwt-decode": "^3.1.2",
"kashe": "1.0.4",
"lodash.flatten": "^4.4.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "4.5.0",
"lodash.isfunction": "3.0.9",
"lodash.isobject": "3.0.2",
"lodash.isundefined": "3.0.1",
"lodash.pick": "^4.4.0",
"lodash.pickby": "^4.6.0",
"lodash.throttle": "^4.1.1",
"lodash.uniqueid": "4.0.1",
"lodash.xor": "^4.5.0",
"lodash": "^4.17.21",
"memoize-one": "5.2.1",
"oidc-client-ts": "^2.2.2",
"react-hook-form": "^7.43.3",
Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/components/accordion/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import uniqueId from 'lodash.uniqueid';
import pickBy from 'lodash.pickby';
import { uniqueId, pickBy } from 'lodash';

import '../../styles/base.module.css';
import styles from './Accordion.module.scss';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _get from 'lodash.get';
import { get } from 'lodash';

import { CookieContentSource, ContentSourceCookieGroup, createContent, setPropsToObject } from './content.builder';
import { CookieContentSource, ContentSourceCookieGroup, createContent } from './content.builder';
import { getCookieContent } from './getContent';
import { CookieData, CookieGroup, Content, Category } from './contexts/ContentContext';

Expand Down Expand Up @@ -155,7 +155,7 @@ describe(`content.builder.ts`, () => {
expect(plainContent.requiredCookies).toBeUndefined();
expect(plainContent.optionalCookies).toBeUndefined();

expect(_get(plainContent, mainTitlePath).indexOf(siteName)).toBe(0);
expect(get(plainContent, mainTitlePath).indexOf(siteName)).toBe(0);
});
});
describe('contentSource.texts', () => {
Expand All @@ -166,14 +166,14 @@ describe(`content.builder.ts`, () => {
...commonContentTestProps,
texts: { sections: { main: { title: newMainTitle } } },
});
expect(_get(contentWithNewMainTitle, mainTitlePath)).toBe(newMainTitle);
expect(_get(contentWithNewMainTitle, mainTextPath)).toBe(_get(defaults, mainTextPath));
expect(get(contentWithNewMainTitle, mainTitlePath)).toBe(newMainTitle);
expect(get(contentWithNewMainTitle, mainTextPath)).toBe(get(defaults, mainTextPath));
const contentWithNewDetailsText = createContent({
...commonContentTestProps,
texts: { sections: { details: { text: newDetailsText } } },
});
expect(_get(contentWithNewDetailsText, detailsTextPath)).toBe(newDetailsText);
expect(_get(contentWithNewDetailsText, detailsTitlePath)).toBe(_get(defaults, detailsTitlePath));
expect(get(contentWithNewDetailsText, detailsTextPath)).toBe(newDetailsText);
expect(get(contentWithNewDetailsText, detailsTitlePath)).toBe(get(defaults, detailsTitlePath));
});
});
describe('contentSource.language', () => {
Expand Down Expand Up @@ -893,89 +893,4 @@ describe(`content.builder.ts`, () => {
);
});
});
describe('setPropsToObject merges given value/object to the target object', () => {
type AnyObject = { [key: string]: unknown };
type TargetObject = { [key: string]: TargetObject | string };
it('Path indicates where to merge. Path is a given as a comma delimetered path.', () => {
const target = setPropsToObject({}, 'a', 'valueOfA');
expect(target).toEqual({ a: 'valueOfA' });

const target2 = setPropsToObject({}, 'a.a', 'valueOfAA');
expect(target2).toEqual({ a: { a: 'valueOfAA' } });

const target3 = setPropsToObject({}, 'a.b.c.d.e.f', 'valueOfABCDEF');
expect(target3).toEqual({
a: {
b: {
c: {
d: {
e: {
f: 'valueOfABCDEF',
},
},
},
},
},
});
});
it('existing props are not overridden or copied', () => {
const target: TargetObject & { obj1: { obj2?: AnyObject; newObj2?: { newObj3?: AnyObject } } } = {
prop1: 'valueOfProp1',
obj1: { obj2: { value: 'valueOfObj2' } },
};

// save refs to objects to check refs do not change
const { obj1: obj1Ref } = target;
const { obj2: obj2Ref } = target.obj1 as TargetObject;

// objects added later
const obj3 = { value: 'valueOfObj3' };
const newObj4 = { newObj5: 'valueOfNewObj5' };

setPropsToObject(target, 'prop2', 'valueOfProp2');
expect(target.prop2).toBe('valueOfProp2');

setPropsToObject(target.obj1.obj2 as AnyObject, 'obj3', obj3);
expect((target.obj1.obj2 as AnyObject).obj3).toBe(obj3);

setPropsToObject(target, 'obj1.newObj2.newObj3', newObj4);
expect((target.obj1.newObj2 as AnyObject).newObj3).toBe(newObj4);

// check initial values/refs have not changed
expect(target.prop1).toBe('valueOfProp1');
expect(target.obj1).toBe(obj1Ref);
expect(target.obj1.obj2).toBe(obj2Ref);
expect((target.obj1.obj2 as AnyObject).value).toBe('valueOfObj2');
});
it('object to merge is not copied, but assigned', () => {
const target: AnyObject = {};
const mergedObject1 = { obj: 'valueOfObj', object2: {} };
const mergedObject2 = { obj2: 'valueOfObj2' };
const finalObject1 = { obj3: 'valueOfObj3' };

setPropsToObject(target, 'object1', mergedObject1);
expect(target.object1).toEqual(mergedObject1);

setPropsToObject(target, 'object1.object2', mergedObject2);
expect((target.object1 as AnyObject).object2).toEqual(mergedObject2);

setPropsToObject(target, 'object1', finalObject1);
expect(target.object1).toEqual(finalObject1);
expect((target.object1 as AnyObject).object2).toBeUndefined();
});
it('merged value can be anything', () => {
const target: AnyObject = {};
const values = [null, undefined, 1, 'abc', () => undefined, new Map(), Object.create({})];
values.forEach((value, index) => {
const key = `value-${index}`;
setPropsToObject(target, key, value);
expect(target[key]).toBe(value);
});
});
it('Using "_" is prohibited to avoid prototype pollution', () => {
const target: AnyObject = {};
expect(() => setPropsToObject(target, '__proto__.toString', () => 'hello')).toThrow();
expect(target.toString()).not.toBe('hello');
});
});
});
35 changes: 5 additions & 30 deletions packages/react/src/components/cookieConsent/content.builder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import _get from 'lodash.get';
import { get, set } from 'lodash';

import type {
CookieData,
Expand Down Expand Up @@ -143,40 +143,15 @@ function getCommonCookie(language: string, id: string): CookieData {
return cookie;
}

// lodash.set has a high vulnerability without a fix
// replaced with this custom merge function
export function setPropsToObject(
targetObject: Record<string, unknown>,
path: string,
value: unknown,
): Record<string, unknown> {
if (path.includes('_')) {
throw new Error('String "_" is not allowed in the path to avoid prototype pollution');
}
const splitPath = path.split('.');
const lastPath = splitPath.pop();
const targetPointInObject = splitPath.reduce((currentObj, currentPath) => {
if (typeof currentObj[currentPath] === 'undefined') {
// eslint-disable-next-line no-param-reassign
currentObj[currentPath] = Object.create(null);
}
return currentObj[currentPath];
}, targetObject);
if (lastPath) {
targetPointInObject[lastPath] = value;
}
return targetObject;
}

function mergeObjects(target: MergableContent, source: MergableContent, paths: string[]) {
paths.forEach((path) => {
const pickedFromSource = _get(source, path);
const pickedFromSource = get(source, path);
if (pickedFromSource) {
const pickedFromTarget = _get(target, path);
const pickedFromTarget = get(target, path);
if (typeof pickedFromSource === 'string') {
setPropsToObject(target, path, pickedFromSource || pickedFromTarget);
set(target, path, pickedFromSource || pickedFromTarget);
} else {
setPropsToObject(target, path, {
set(target, path, {
...pickedFromTarget,
...pickedFromSource,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import _pick from 'lodash.pick';
import _isObject from 'lodash.isobject';
import _isUndefined from 'lodash.isundefined';
import { pick, isObject, isUndefined } from 'lodash';

import { createCookieController } from './cookieController';

Expand Down Expand Up @@ -48,7 +46,7 @@ function mergeConsents(set1: ConsentObject, set2: ConsentObject, set3?: ConsentO
}

function createConsentsString(consents: ConsentObject): string {
if (!_isObject(consents)) {
if (!isObject(consents)) {
return '{}';
}
return JSON.stringify(consents);
Expand Down Expand Up @@ -107,10 +105,10 @@ export function createStorage(
};

const findConsentSource = (consentName: string, targetStorage: ConsentStorage): ConsentObject | undefined => {
if (!_isUndefined(targetStorage.required[consentName])) {
if (!isUndefined(targetStorage.required[consentName])) {
return targetStorage.required;
}
if (!_isUndefined(targetStorage.optional[consentName])) {
if (!isUndefined(targetStorage.optional[consentName])) {
return targetStorage.optional;
}
return undefined;
Expand Down Expand Up @@ -168,17 +166,17 @@ export default function createConsentController(props: ConsentControllerProps):

const required = mergeConsents(
convertStringArrayToKeyConsentObject(requiredConsents),
_pick(currentConsentsInCookie, requiredConsents),
pick(currentConsentsInCookie, requiredConsents),
);

const optional = mergeConsents(
convertStringArrayToKeyConsentObject(optionalConsents),
_pick(currentConsentsInCookie, optionalConsents),
pick(currentConsentsInCookie, optionalConsents),
);

const unknownConsentKeys = Object.keys(currentConsentsInCookie).filter((key) => !allConsents.includes(key));

const unknown = unknownConsentKeys.length ? _pick(currentConsentsInCookie, unknownConsentKeys) : undefined;
const unknown = unknownConsentKeys.length ? pick(currentConsentsInCookie, unknownConsentKeys) : undefined;

const storage = createStorage({ required, optional, unknown });

Expand Down Expand Up @@ -222,7 +220,7 @@ export default function createConsentController(props: ConsentControllerProps):
rejectAll,
getUnhandledConsents: () => {
const storedCookies = parseConsents(cookieController.get());
return allConsents.filter((key) => _isUndefined(storedCookies[key]));
return allConsents.filter((key) => isUndefined(storedCookies[key]));
},
save,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import uniqueId from 'lodash.uniqueid';
import { uniqueId } from 'lodash';

import { Button } from '../../button';
import { Combobox } from './Combobox';
Expand Down
3 changes: 1 addition & 2 deletions packages/react/src/components/dropdown/combobox/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
/* eslint-disable react/destructuring-assignment */
import React, { useRef, useState, KeyboardEvent, FocusEvent, FocusEventHandler, useMemo, useCallback } from 'react';
import { useCombobox, useMultipleSelection } from 'downshift';
import isEqual from 'lodash.isequal';
import uniqueId from 'lodash.uniqueid';
import { isEqual, uniqueId } from 'lodash';
import { useVirtual } from 'react-virtual';

import '../../../styles/base.module.css';
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/dropdown/dropdownUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import isEqual from 'lodash.isequal';
import { isEqual } from 'lodash';

export const DROPDOWN_MENU_ITEM_HEIGHT = 52;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { action } from '@storybook/addon-actions';
import uniqueId from 'lodash.uniqueid';
import { uniqueId } from 'lodash';

import { Button } from '../../button';
import { Select } from './Select';
Expand Down
Loading

0 comments on commit 7b71112

Please sign in to comment.