All files / modules/85-cv/components/JsonSelector JsonSelector.tsx

85.29% Statements 29/34
76.47% Branches 13/17
75% Functions 6/8
85.29% Lines 29/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110              36x 36x 36x 36x 36x               36x     15x         15x 15x 66x 12x       12x       54x                   36x 3x   3x 3x     36x 66x 66x 45x   66x     36x 3x   3x             3x       66x               66x                                             36x  
/*
 * Copyright 2021 Harness Inc. All rights reserved.
 * Use of this source code is governed by the PolyForm Shield 1.0.0 license
 * that can be found in the licenses directory at the root of this repository, also available at
 * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
 */
 
import React from 'react'
import classnames from 'classnames'
import { Container } from '@wings-software/uicore'
import isUndefined from 'lodash/isUndefined'
import css from './JsonSelector.module.scss'
 
export interface JsonSelectorProps {
  json: Record<string, any>
  className?: string
  onPathSelect?: (path: string) => void
}
 
const MAX_NESTING_LEVEL = 100
 
function visit(json: Record<string, any>, rows: Array<any>, path: Array<string> = []) {
  Iif (path.length === MAX_NESTING_LEVEL) {
    // This is the simple guard since the algorithm currently doesn't check
    // for backward references - it expects "config" json.
    throw new Error('max nesting level was reached.')
  }
  Eif (json) {
    for (const entry of Object.entries(json)) {
      if (typeof entry[1] === 'object' && entry[1] !== null) {
        rows.push({
          key: entry[0],
          path
        })
        visit(entry[1], rows, [...path, entry[0]])
        // TODO - When exactly we need to have empty rows?
        // rows.push(null);
      } else {
        rows.push({
          key: entry[0],
          value: entry[1],
          path
        })
      }
    }
  }
}
 
const calculateRows = (json: any) => {
  const rows: Array<any> = []
  // TODO - memoize ??
  visit(json, rows)
  return rows
}
 
const ident = (nestingLevel: number): string => {
  let ret = ''
  while (nestingLevel-- > 0) {
    ret += '\u00A0\u00A0\u00A0\u00A0'
  }
  return ret
}
 
const JsonSelector: React.FC<JsonSelectorProps> = ({ json, className, onPathSelect }) => {
  const rows: Array<any> = calculateRows(json)
 
  const onSelect = (row: any) => {
    if (onPathSelect) {
      const path = [...row.path, row.key]
      onPathSelect(path.join('.'))
    }
  }
 
  return (
    <Container className={classnames(css.jsonSelector, className)}>
      <div className={css.lineNumbersCol}>
        {rows.map((_, index) => (
          <div key={index} className={css.lineNumber}>
            {index + 1}
          </div>
        ))}
      </div>
      <div className={css.panel}>
        <div className={css.contentWrap}>
          {rows.map((row, index) => (
            <div key={index} className={css.editorRow}>
              {!!row && (
                <React.Fragment>
                  <span>{ident(row.path.length)}</span>
                  {isUndefined(row.value) && <span>{row.key}</span>}
                  {!isUndefined(row.value) && (
                    <span>
                      <span onClick={() => onSelect(row)} className={css.selectableKey}>
                        {row.key}
                      </span>
                      :&nbsp;{row.value}
                    </span>
                  )}
                </React.Fragment>
              )}
            </div>
          ))}
        </div>
      </div>
    </Container>
  )
}
 
export default JsonSelector