|
17 | 17 | import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
18 | 18 | import { components } from 'react-select'
|
19 | 19 | import * as Sentry from '@sentry/browser'
|
20 |
| -import * as jsonpatch from 'fast-json-patch' |
21 |
| -import { JSONPath } from 'jsonpath-plus' |
22 | 20 | import moment from 'moment'
|
23 | 21 | import { useLocation } from 'react-router-dom'
|
24 | 22 | import { toast } from 'react-toastify'
|
@@ -622,79 +620,110 @@ export const getFilteredChartVersions = (charts, selectedChartType) =>
|
622 | 620 | chartRefId: item.chartRefId,
|
623 | 621 | }))
|
624 | 622 |
|
625 |
| -function removeEmptyObjectKeysAndNullValues(obj, originaljsonCopy) { |
626 |
| - // It recursively removes empty object keys and empty array keys |
627 |
| - for (const key in obj) { |
628 |
| - if (Array.isArray(obj[key])) { |
629 |
| - // Check if the array is empty |
630 |
| - if (obj[key].length !== 0) { |
631 |
| - obj[key].forEach((element, index) => { |
632 |
| - if (element === null || (typeof element === 'object' && Object.keys(element).length === 0)) |
633 |
| - obj[key].splice(index, 1) |
634 |
| - else removeEmptyObjectKeysAndNullValues(element, originaljsonCopy[key][index]) |
635 |
| - }) |
636 |
| - } |
637 |
| - if (obj[key].length === 0 && originaljsonCopy[key].length !== 0) { |
638 |
| - delete obj[key] |
639 |
| - } |
640 |
| - } else if (obj[key] && typeof obj[key] === 'object') { |
641 |
| - if ( |
642 |
| - removeEmptyObjectKeysAndNullValues(obj[key], originaljsonCopy[key]) && |
643 |
| - Object.keys(originaljsonCopy[key]).length !== 0 |
644 |
| - ) { |
645 |
| - delete obj[key] |
646 |
| - } |
647 |
| - } else if (obj[key] === undefined) { |
648 |
| - delete obj[key] |
649 |
| - } |
| 623 | +/** |
| 624 | + * Removes nulls from the arrays in the provided object |
| 625 | + * @param {object} object from which we need to delete nulls in its arrays |
| 626 | + * @returns object after removing (in-place) the null items in arrays |
| 627 | + */ |
| 628 | +export const recursivelyRemoveNullsFromArraysInObject = (object: object) => { |
| 629 | + // NOTE: typeof null === 'object' |
| 630 | + if (typeof object !== 'object' || !object) { |
| 631 | + return object |
| 632 | + } |
| 633 | + if (Array.isArray(object)) { |
| 634 | + return object.filter((item) => { |
| 635 | + recursivelyRemoveNullsFromArraysInObject(item) |
| 636 | + return !!item |
| 637 | + }) |
650 | 638 | }
|
651 |
| - return Object.keys(obj).length === 0 |
| 639 | + Object.keys(object).forEach((key) => { |
| 640 | + object[key] = recursivelyRemoveNullsFromArraysInObject(object[key]) |
| 641 | + }) |
| 642 | + return object |
652 | 643 | }
|
653 | 644 |
|
654 |
| -export function getUnlockedJSON(json, jsonPathArray, removeParentKeysAndEmptyArrays = false) { |
655 |
| - if (!jsonPathArray.length) return { newDocument: json, removedPatches: [] } |
| 645 | +const _joinObjects = (A: object, B: object) => { |
| 646 | + if (typeof A !== 'object' || typeof B !== 'object' || !B) { |
| 647 | + return |
| 648 | + } |
| 649 | + Object.keys(B).forEach((key) => { |
| 650 | + if (!A[key]) { |
| 651 | + A[key] = structuredClone(B[key]) |
| 652 | + return |
| 653 | + } |
| 654 | + _joinObjects(A[key], B[key]) |
| 655 | + }) |
| 656 | +} |
656 | 657 |
|
657 |
| - const jsonCopy = JSON.parse(JSON.stringify(json)) |
658 |
| - const originaljsonCopy = JSON.parse(JSON.stringify(json)) |
659 |
| - const removedPatches = [] |
660 |
| - const patches = jsonPathArray.flatMap((jsonPath) => { |
661 |
| - const pathsToRemove = JSONPath({ path: jsonPath, json: jsonCopy, resultType: 'all' }) |
662 |
| - // reversing patches to handle correct array index deletion |
663 |
| - pathsToRemove.reverse() |
664 |
| - return pathsToRemove.map((result) => { |
665 |
| - // storing removed patches to have functionality of undo |
666 |
| - removedPatches.push({ op: 'add', path: result.pointer, value: result.value }) |
667 |
| - return { op: 'remove', path: result.pointer } |
668 |
| - }) |
| 658 | +/** |
| 659 | + * Merges the objects into one object |
| 660 | + * @param {object[]} objects list of js objects |
| 661 | + * @returns object after the merge |
| 662 | + */ |
| 663 | +export const joinObjects = (objects: object[]) => { |
| 664 | + const join = {} |
| 665 | + objects.forEach((object) => { |
| 666 | + _joinObjects(join, object) |
669 | 667 | })
|
670 |
| - const { newDocument } = jsonpatch.applyPatch(jsonCopy, patches) |
671 |
| - if (removeParentKeysAndEmptyArrays) removeEmptyObjectKeysAndNullValues(newDocument, originaljsonCopy) |
672 |
| - return { newDocument, removedPatches: removedPatches.reverse() } |
673 |
| -} |
674 |
| - |
675 |
| -export function getLockedJSON(json, jsonPathArray: string[]) { |
676 |
| - const jsonCopy = JSON.parse(JSON.stringify(json)) |
677 |
| - const resultJson = {} |
678 |
| - jsonPathArray.forEach((jsonPath) => { |
679 |
| - const elements = JSONPath({ path: jsonPath, json: jsonCopy, resultType: 'all' }) |
680 |
| - elements.forEach((element) => { |
681 |
| - const pathArray: string[] = JSONPath.toPathArray(element.path) |
682 |
| - const lastPath = pathArray.pop() |
683 |
| - let current = resultJson |
684 |
| - for (let i = 0; i < pathArray.length; i++) { |
685 |
| - const key = isNaN(Number(pathArray[i])) ? pathArray[i] : parseInt(pathArray[i]) |
686 |
| - if (!current[key]) { |
687 |
| - current[key] = isNaN(Number(pathArray[i + 1] ?? lastPath)) ? {} : [] |
688 |
| - } |
689 |
| - current = current[key] |
690 |
| - } |
691 |
| - const key = isNaN(Number(lastPath)) ? lastPath : parseInt(lastPath) |
692 |
| - current[key] = element.value |
| 668 | + return join |
| 669 | +} |
| 670 | + |
| 671 | +const buildObjectFromPathTokens = (index: number, tokens: string[], value: any) => { |
| 672 | + if (index === tokens.length) { |
| 673 | + return value |
| 674 | + } |
| 675 | + const key = tokens[index] |
| 676 | + const numberKey = Number(key) |
| 677 | + const isKeyNumber = !Number.isNaN(numberKey) |
| 678 | + return isKeyNumber |
| 679 | + ? [...Array(numberKey).fill(null), buildObjectFromPathTokens(index + 1, tokens, value)] |
| 680 | + : { [key]: buildObjectFromPathTokens(index + 1, tokens, value) } |
| 681 | +} |
| 682 | + |
| 683 | +/** |
| 684 | + * Builds an object from the provided path |
| 685 | + * @param {string} path JSON pointer path of the form /path/to/1/... |
| 686 | + * @param {any} 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 | +/** |
| 712 | + * Returns all the substrings from 0th index to progressively next regex match position |
| 713 | + * Eg. Suppose a string is /path/to/folder then it will return [/path, /path/to, /path/to/folder] |
| 714 | + * @param strings list of strings |
| 715 | + * @param regex that matches the separators for the substrings |
| 716 | + * @returns set of substrings |
| 717 | + */ |
| 718 | +export const powerSetOfSubstringsFromStart = (strings: string[], regex: RegExp) => |
| 719 | + strings.flatMap((key) => { |
| 720 | + const _keys = [key] |
| 721 | + const positions = getRegexMatchPositions(key, regex) |
| 722 | + positions.forEach((position) => { |
| 723 | + _keys.push(key.slice(0, position)) |
693 | 724 | })
|
| 725 | + return _keys |
694 | 726 | })
|
695 |
| - // eslint-disable-next-line dot-notation |
696 |
| - return resultJson['$'] |
697 |
| -} |
698 | 727 |
|
699 | 728 | /**
|
700 | 729 | * Returns a debounced variant of the function
|
@@ -982,7 +1011,7 @@ export function useKeyDown() {
|
982 | 1011 | export const DropdownIndicator = (props) => {
|
983 | 1012 | return (
|
984 | 1013 | <components.DropdownIndicator {...props}>
|
985 |
| - <ArrowDown className="icon-dim-20 icon-n5" /> |
| 1014 | + <ArrowDown className="icon-dim-20 icon-n6" /> |
986 | 1015 | </components.DropdownIndicator>
|
987 | 1016 | )
|
988 | 1017 | }
|
|
0 commit comments