|
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'
|
@@ -619,79 +617,111 @@ export const getFilteredChartVersions = (charts, selectedChartType) =>
|
619 | 617 | chartRefId: item.chartRefId,
|
620 | 618 | }))
|
621 | 619 |
|
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 |
643 | 638 | }
|
644 |
| - } else if (obj[key] === undefined) { |
645 |
| - delete obj[key] |
646 | 639 | }
|
647 |
| - } |
648 |
| - return Object.keys(obj).length === 0 |
| 640 | + return false |
| 641 | + }) |
649 | 642 | }
|
650 | 643 |
|
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 | +} |
653 | 656 |
|
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) |
666 | 666 | })
|
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 |
687 | 720 | }
|
688 |
| - const key = isNaN(Number(lastPath)) ? lastPath : parseInt(lastPath) |
689 |
| - current[key] = element.value |
| 721 | + _keys.push(key.slice(0, position)) |
690 | 722 | })
|
| 723 | + return _keys |
691 | 724 | })
|
692 |
| - // eslint-disable-next-line dot-notation |
693 |
| - return resultJson['$'] |
694 |
| -} |
695 | 725 |
|
696 | 726 | /**
|
697 | 727 | * Returns a debounced variant of the function
|
|
0 commit comments