Skip to content

Commit 577f078

Browse files
committed
feat: remove dep of lock config diff from api
1 parent d184b7c commit 577f078

File tree

1 file changed

+96
-66
lines changed

1 file changed

+96
-66
lines changed

src/Common/Helper.tsx

Lines changed: 96 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
1818
import { components } from 'react-select'
1919
import * as Sentry from '@sentry/browser'
20-
import * as jsonpatch from 'fast-json-patch'
21-
import { JSONPath } from 'jsonpath-plus'
2220
import moment from 'moment'
2321
import { useLocation } from 'react-router-dom'
2422
import { toast } from 'react-toastify'
@@ -619,79 +617,111 @@ export const getFilteredChartVersions = (charts, selectedChartType) =>
619617
chartRefId: item.chartRefId,
620618
}))
621619

622-
function removeEmptyObjectKeysAndNullValues(obj, originaljsonCopy) {
623-
// It recursively removes empty object keys and empty array keys
624-
for (const key in obj) {
625-
if (Array.isArray(obj[key])) {
626-
// Check if the array is empty
627-
if (obj[key].length !== 0) {
628-
obj[key].forEach((element, index) => {
629-
if (element === null || (typeof element === 'object' && Object.keys(element).length === 0))
630-
obj[key].splice(index, 1)
631-
else removeEmptyObjectKeysAndNullValues(element, originaljsonCopy[key][index])
632-
})
633-
}
634-
if (obj[key].length === 0 && originaljsonCopy[key].length !== 0) {
635-
delete obj[key]
636-
}
637-
} else if (obj[key] && typeof obj[key] === 'object') {
638-
if (
639-
removeEmptyObjectKeysAndNullValues(obj[key], originaljsonCopy[key]) &&
640-
Object.keys(originaljsonCopy[key]).length !== 0
641-
) {
642-
delete obj[key]
620+
/**
621+
* This function takes a list of strings and checks if the strings
622+
* have prefix matches in the list of prefixes
623+
* @param strings check if a @string has a prefix match in @prefixes
624+
* @param prefixes list of prefixes
625+
* @returns array of booleans corresponding to each string in @strings
626+
*/
627+
export const getPrefixMatches = (strings: string[], prefixes: string[]) => {
628+
// NOTE: a trie could have been used but it would have been memory inefficient
629+
const set = new Set(prefixes)
630+
return strings.map((string) => {
631+
for (let index = 0; index <= string.length; index++) {
632+
// NOTE: slice in v8 is O(1) https://mrale.ph/blog/2016/11/23/making-less-dart-faster.html
633+
// due to using SlicedString construct that only keeps pointer to original string
634+
// alternative tries would be tree structure (hard to cache)
635+
const slice = string.slice(0, index)
636+
if (set.has(slice)) {
637+
return true
643638
}
644-
} else if (obj[key] === undefined) {
645-
delete obj[key]
646639
}
647-
}
648-
return Object.keys(obj).length === 0
640+
return false
641+
})
649642
}
650643

651-
export function getUnlockedJSON(json, jsonPathArray, removeParentKeysAndEmptyArrays = false) {
652-
if (!jsonPathArray.length) return { newDocument: json, removedPatches: [] }
644+
const _joinObjects = (A: object, B: object) => {
645+
if (typeof A !== 'object' || typeof B !== 'object' || !B) {
646+
return
647+
}
648+
Object.keys(B).forEach((key) => {
649+
if (!A[key]) {
650+
A[key] = structuredClone(B[key])
651+
return
652+
}
653+
_joinObjects(A[key], B[key])
654+
})
655+
}
653656

654-
const jsonCopy = JSON.parse(JSON.stringify(json))
655-
const originaljsonCopy = JSON.parse(JSON.stringify(json))
656-
const removedPatches = []
657-
const patches = jsonPathArray.flatMap((jsonPath) => {
658-
const pathsToRemove = JSONPath({ path: jsonPath, json: jsonCopy, resultType: 'all' })
659-
// reversing patches to handle correct array index deletion
660-
pathsToRemove.reverse()
661-
return pathsToRemove.map((result) => {
662-
// storing removed patches to have functionality of undo
663-
removedPatches.push({ op: 'add', path: result.pointer, value: result.value })
664-
return { op: 'remove', path: result.pointer }
665-
})
657+
/**
658+
* Merges @objects into one
659+
* @param objects list of js objects
660+
* @returns object after the merge
661+
*/
662+
export const joinObjects = (objects: object[]) => {
663+
const join = {}
664+
objects.forEach((object) => {
665+
_joinObjects(join, object)
666666
})
667-
const { newDocument } = jsonpatch.applyPatch(jsonCopy, patches)
668-
if (removeParentKeysAndEmptyArrays) removeEmptyObjectKeysAndNullValues(newDocument, originaljsonCopy)
669-
return { newDocument, removedPatches: removedPatches.reverse() }
670-
}
671-
672-
export function getLockedJSON(json, jsonPathArray: string[]) {
673-
const jsonCopy = JSON.parse(JSON.stringify(json))
674-
const resultJson = {}
675-
jsonPathArray.forEach((jsonPath) => {
676-
const elements = JSONPath({ path: jsonPath, json: jsonCopy, resultType: 'all' })
677-
elements.forEach((element) => {
678-
const pathArray: string[] = JSONPath.toPathArray(element.path)
679-
const lastPath = pathArray.pop()
680-
let current = resultJson
681-
for (let i = 0; i < pathArray.length; i++) {
682-
const key = isNaN(Number(pathArray[i])) ? pathArray[i] : parseInt(pathArray[i])
683-
if (!current[key]) {
684-
current[key] = isNaN(Number(pathArray[i + 1] ?? lastPath)) ? {} : []
685-
}
686-
current = current[key]
667+
return join
668+
}
669+
670+
const buildObjectFromPathTokens = (index: number, tokens: string[], value: any) => {
671+
if (index === tokens.length) {
672+
return value
673+
}
674+
const key = tokens[index]
675+
const numberKey = Number(key)
676+
const isKeyNumber = !Number.isNaN(numberKey)
677+
return isKeyNumber
678+
? [...Array(numberKey).fill(null), buildObjectFromPathTokens(index + 1, tokens, value)]
679+
: { [key]: buildObjectFromPathTokens(index + 1, tokens, value) }
680+
}
681+
682+
// TODO: learn how to write js doc comments
683+
/**
684+
* Builds an object from the provided @path
685+
* @param path JSON pointer path of the form /path/to/1/...
686+
* @param value property value
687+
* @returns final object formed from the path
688+
*/
689+
export const buildObjectFromPath = (path: string, value: any) => {
690+
const parts = path.split('/')
691+
// NOTE: since the path has a leading / we need to delete the first element
692+
parts.splice(0, 1)
693+
return buildObjectFromPathTokens(0, parts, value)
694+
}
695+
696+
/**
697+
* Returns the list of indices at which the regex matched
698+
* @param string the string to process
699+
* @param regex @RegExp
700+
* @returns array of indices where the @regex matched in @string
701+
*/
702+
export const getRegexMatchPositions = (string: string, regex: RegExp): number[] => {
703+
const pos = string.search(regex)
704+
if (pos < 0) {
705+
return []
706+
}
707+
const nextToken = string.slice(pos + 1)
708+
return [pos, ...getRegexMatchPositions(nextToken, regex).map((n) => n + pos + 1)]
709+
}
710+
711+
// TODO: write js doc comment
712+
export const powerSetOfSubstringsFromStart = (strings: string[]) =>
713+
strings.flatMap((key) => {
714+
const _keys = [key]
715+
const positions = getRegexMatchPositions(key, /\.|\[|\]/g)
716+
positions.forEach((position) => {
717+
const ch = key.charAt(position)
718+
if (ch === ']') {
719+
return
687720
}
688-
const key = isNaN(Number(lastPath)) ? lastPath : parseInt(lastPath)
689-
current[key] = element.value
721+
_keys.push(key.slice(0, position))
690722
})
723+
return _keys
691724
})
692-
// eslint-disable-next-line dot-notation
693-
return resultJson['$']
694-
}
695725

696726
/**
697727
* Returns a debounced variant of the function

0 commit comments

Comments
 (0)