Skip to content

Commit 1ba41aa

Browse files
authored
Merge pull request #192 from devtron-labs/feat/gui-view-hidden
feat: hide object properties in schema through the hidden property
2 parents d4b195a + bb17690 commit 1ba41aa

File tree

14 files changed

+6785
-53
lines changed

14 files changed

+6785
-53
lines changed

package-lock.json

Lines changed: 6644 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "0.1.9",
3+
"version": "0.1.10",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
@@ -95,6 +95,7 @@
9595
"ansi_up": "^5.2.1",
9696
"fast-json-patch": "^3.1.1",
9797
"react-dates": "^21.8.0",
98+
"jsonpath-plus": "^9.0.0",
9899
"react-monaco-editor": "^0.54.0",
99100
"sass": "^1.69.7",
100101
"tslib": "^2.4.1"
@@ -108,4 +109,4 @@
108109
"monaco-editor": "0.44.0"
109110
}
110111
}
111-
}
112+
}

src/Common/CodeEditor/CodeEditor.tsx

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ function useCodeEditorContext() {
6565
return context
6666
}
6767

68+
/**
69+
* NOTE: once monaco editor mounts it doesn't update it's internal onChange state.
70+
* Since most of the time onChange methods are declared inside the render body of a component
71+
* we should use the latest references of onChange.
72+
* Please see: https://github.com/react-monaco-editor/react-monaco-editor/issues/704
73+
* Thus as a hack we are using this objects reference to call the latest onChange reference
74+
*/
75+
const _onChange = {
76+
onChange: null
77+
}
78+
6879
const CodeEditor: React.FC<CodeEditorInterface> & CodeEditorComposition = React.memo(
6980
({
7081
value,
@@ -100,7 +111,7 @@ const CodeEditor: React.FC<CodeEditorInterface> & CodeEditorComposition = React.
100111
const monacoRef = useRef(null)
101112
const { width, height: windowHeight } = useWindowSize()
102113
const memoisedReducer = React.useCallback(CodeEditorReducer, [])
103-
const [state, dispatch] = useReducer(memoisedReducer, initialState({mode, theme, value, diffView, noParsing}))
114+
const [state, dispatch] = useReducer(memoisedReducer, initialState({ mode, theme, value, diffView, noParsing }))
104115
const [, json, yamlCode, error] = useJsonYaml(state.code, tabSize, state.mode, !state.noParsing)
105116
const [, originalJson, originlaYaml] = useJsonYaml(defaultValue, tabSize, state.mode, !state.noParsing)
106117
monaco.editor.defineTheme(CodeEditorThemesKeys.vsDarkDT, {
@@ -193,15 +204,19 @@ const CodeEditor: React.FC<CodeEditorInterface> & CodeEditorComposition = React.
193204
editorRef.current.layout()
194205
}, [width, windowHeight])
195206

196-
useEffect(() => {
197-
if (onChange) {
198-
onChange(state.code)
199-
}
200-
}, [state.code])
207+
/**
208+
* NOTE: Please see @_onChange variable
209+
*/
210+
_onChange.onChange = onChange
211+
212+
const setCode = (value: string) => {
213+
dispatch({ type: 'setCode', value })
214+
_onChange.onChange?.(value)
215+
}
201216

202217
useEffect(() => {
203218
if (noParsing) {
204-
dispatch({ type: 'setCode', value })
219+
setCode(value)
205220

206221
return
207222
}
@@ -220,7 +235,7 @@ const CodeEditor: React.FC<CodeEditorInterface> & CodeEditorComposition = React.
220235
if (obj) {
221236
final = state.mode === 'json' ? JSON.stringify(obj, null, tabSize) : YAMLStringify(obj)
222237
}
223-
dispatch({ type: 'setCode', value: final })
238+
setCode(final)
224239
}, [value, noParsing])
225240

226241
useEffect(() => {
@@ -234,12 +249,12 @@ const CodeEditor: React.FC<CodeEditorInterface> & CodeEditorComposition = React.
234249
}, [focus])
235250

236251
function handleOnChange(newValue, e) {
237-
dispatch({ type: 'setCode', value: newValue })
252+
setCode(newValue)
238253
}
239254

240255
function handleLanguageChange(mode: 'json' | 'yaml') {
241256
dispatch({ type: 'changeLanguage', value: mode })
242-
dispatch({ type: 'setCode', value: mode === 'json' ? json : yamlCode })
257+
setCode(mode === 'json' ? json : yamlCode)
243258
}
244259

245260
const options: monaco.editor.IEditorConstructionOptions = {

src/Common/Helper.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616

1717
import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
18+
import { JSONPath, JSONPathOptions } from 'jsonpath-plus'
19+
import { compare as compareJSON, applyPatch } from 'fast-json-patch'
1820
import { components } from 'react-select'
1921
import * as Sentry from '@sentry/browser'
2022
import moment from 'moment'
@@ -725,6 +727,20 @@ export const powerSetOfSubstringsFromStart = (strings: string[], regex: RegExp)
725727
return _keys
726728
})
727729

730+
export const convertJSONPointerToJSONPath = (pointer: string) =>
731+
pointer.replace(/\//g, '.').replace(/\./, '$.')
732+
733+
export const flatMapOfJSONPaths = (
734+
paths: string[],
735+
json: object,
736+
resultType: JSONPathOptions['resultType'] = 'path',
737+
): string[] => paths.flatMap((path) => JSONPath({ path, json, resultType }))
738+
739+
export const applyCompareDiffOnUneditedDocument = (uneditedDocument: object, editedDocument: object) => {
740+
const patch = compareJSON(uneditedDocument, editedDocument)
741+
return applyPatch(uneditedDocument, patch).newDocument
742+
}
743+
728744
/**
729745
* Returns a debounced variant of the function
730746
*/

src/Common/RJSF/Form.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React from 'react'
1817
import RJSF from '@rjsf/core'
1918
import RJSFValidator from '@rjsf/validator-ajv8'
2019

src/Common/RJSF/rjsfForm.scss

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
// This is added as the array field itself has a margin bottom
4040
margin: 0;
4141
}
42+
43+
& legend {
44+
display: none;
45+
}
46+
47+
&:has(+ div) {
48+
margin-bottom: 8px;
49+
}
4250
}
4351

4452
&__additional-fields,
@@ -53,15 +61,15 @@
5361
&:has(> .remove-btn__container)::after {
5462
border-radius: 4px;
5563
transform: translate(-50%, -50%);
56-
top: 50%;
57-
left: 50%;
64+
top: 50%;
65+
left: 50%;
5866
background: transparent;
5967
z-index: -1;
6068
position: absolute;
6169
/* NOTE: this overlay is exactly 6px wider & taller on all sides
62-
* therefore the 12px (6 + 6) on height & width */
63-
height: calc(100% + 12px);
64-
width: calc(100% + 12px);
70+
* therefore the 8px (4 + 4) on height & width */
71+
height: calc(100% + 8px);
72+
width: calc(100% + 8px);
6573
content: '';
6674
transition: all 100ms ease-out;
6775
}

src/Common/RJSF/templates/ArrayFieldItemTemplate.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React, { CSSProperties } from 'react'
1817
import { ArrayFieldTemplateItemType } from '@rjsf/utils'
1918

2019
export const ArrayFieldItemTemplate = ({
2120
children,
22-
className,
2321
disabled,
2422
hasToolbar,
2523
hasRemove,
@@ -32,7 +30,7 @@ export const ArrayFieldItemTemplate = ({
3230
const { RemoveButton } = registry.templates.ButtonTemplates
3331

3432
return (
35-
<div className="dc__position-rel display-grid rjsf-form-template__array-field-item flex-align-center mb-12">
33+
<div className="dc__position-rel display-grid rjsf-form-template__array-field-item flex-align-center">
3634
{children}
3735
<div className="dc__position-abs remove-btn__container" style={{ right: "-28px", top: "9px" }}>
3836
{hasToolbar && hasRemove && (

src/Common/RJSF/templates/ButtonTemplates/AddButton.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React, { useEffect, useState, useRef } from 'react'
1817
import { IconButtonProps } from '@rjsf/utils'
1918

20-
import { ConditionalWrap } from '../../../Helper'
21-
import { getTippyWrapperWithContent } from '../../utils'
19+
import Tippy from '../../../Tippy'
2220
import { ADD_BUTTON_WIDTH } from '../../constants'
2321
import { ReactComponent as PlusIcon } from '../../../../Assets/Icon/ic-add.svg'
2422

@@ -30,26 +28,27 @@ export const AddButton = ({
3028
uiSchema,
3129
...props
3230
}: IconButtonProps & Partial<Record<'label', string>>) => {
33-
const [showTippy, setShowTippy] = useState(false)
34-
const buttonRef = useRef<HTMLButtonElement>(null)
3531
const content = `Add ${label}`
3632

37-
useEffect(() => setShowTippy(buttonRef.current?.offsetWidth >= ADD_BUTTON_WIDTH.MAX_WIDTH_VALUE), [buttonRef.current])
38-
3933
return (
4034
<div className="flexbox flex-justify-start">
41-
<ConditionalWrap condition={showTippy} wrap={getTippyWrapperWithContent(content)}>
35+
<Tippy
36+
className="default-tt dc__word-break"
37+
arrow={false}
38+
placement="right"
39+
content={content}
40+
truncateWidth={ADD_BUTTON_WIDTH.MAX_WIDTH_VALUE}
41+
>
4242
<button
43-
ref={buttonRef}
4443
{...props}
4544
type="button"
4645
className={`dc__outline-none-imp p-0 dc__transparent flex dc__gap-4 cursor ${ADD_BUTTON_WIDTH.MAX_WIDTH_CLASSNAME}`}
4746
title="Add"
4847
>
4948
<PlusIcon className="icon-dim-16 fcb-5" />
50-
<span className="cb-5 fs-13 lh-20 dc__truncate">{content}</span>
49+
<span className="cb-5 fs-13 lh-34 dc__truncate">{content}</span>
5150
</button>
52-
</ConditionalWrap>
51+
</Tippy>
5352
</div>
5453
)
5554
}

src/Common/RJSF/templates/Field.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export const Field = (props: FieldTemplateProps) => {
4949
return hidden ? (
5050
<div className="hidden">{children}</div>
5151
) : (
52-
<div className={`${classNames} mb-12`}>
52+
// NOTE: need to override the margins of default rjsf css
53+
<div className={`${classNames} mb-0`}>
5354
{showTitle && (
5455
<TitleField
5556
id={id}

src/Common/RJSF/templates/ObjectFieldTemplate.tsx

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import React from 'react'
18-
import {
19-
ObjectFieldTemplatePropertyType,
20-
ObjectFieldTemplateProps,
21-
canExpand,
22-
getUiOptions,
23-
titleId,
24-
} from '@rjsf/utils'
17+
import { ObjectFieldTemplateProps, canExpand, titleId } from '@rjsf/utils'
2518
import { FieldRowWithLabel } from '../common/FieldRow'
2619
import { TitleField } from './TitleField'
2720
import { AddButton } from './ButtonTemplates'
21+
import { JSONPath } from 'jsonpath-plus'
22+
import { RJSFFormSchema } from '../types'
2823

2924
const Field = ({
3025
disabled,
@@ -38,7 +33,7 @@ const Field = ({
3833
schema,
3934
title,
4035
uiSchema,
41-
}: ObjectFieldTemplateProps) => {
36+
}: ObjectFieldTemplateProps<any, RJSFFormSchema, any>) => {
4237
const hasAdditionalProperties = !!schema.additionalProperties
4338

4439
const ActionButton = canExpand(schema, uiSchema, formData) && (
@@ -52,7 +47,20 @@ const Field = ({
5247
/>
5348
)
5449

55-
const Properties = properties.map((prop: ObjectFieldTemplatePropertyType) => prop.content)
50+
const Properties = properties
51+
.filter((prop) => {
52+
const hiddenSchemaProp = schema.properties?.[prop.name]?.hidden
53+
if (!hiddenSchemaProp) {
54+
return true
55+
}
56+
const value = JSONPath({ path: hiddenSchemaProp.match, json: formData })?.[0]
57+
const isHidden = value === undefined || hiddenSchemaProp.condition === value
58+
// NOTE: if should be hidden then filter it out i.e return false
59+
return !isHidden
60+
})
61+
// NOTE: we probably should use uiSchema instead?
62+
.sort((prop) => (schema.properties?.[prop.name]?.type === 'boolean' ? -1 : 1))
63+
.map((prop) => prop.content)
5664

5765
if (hasAdditionalProperties) {
5866
if (properties.length) {
@@ -85,9 +93,8 @@ const Field = ({
8593
)
8694
}
8795

88-
export const ObjectFieldTemplate = (props: ObjectFieldTemplateProps) => {
96+
export const ObjectFieldTemplate = (props: ObjectFieldTemplateProps<any, RJSFFormSchema, any>) => {
8997
const { idSchema, registry, required, schema, title, uiSchema, description } = props
90-
const options = getUiOptions(uiSchema)
9198
const hasAdditionalProperties = !!schema.additionalProperties
9299
const showTitle = title && !hasAdditionalProperties
93100

@@ -106,11 +113,11 @@ export const ObjectFieldTemplate = (props: ObjectFieldTemplateProps) => {
106113
)}
107114
{/* Not adding the border and padding for non-objects and root schema */}
108115
<div
109-
className={
116+
className={`${
110117
schema.properties && !hasAdditionalProperties && idSchema.$id !== 'root'
111118
? 'dc__border-left pl-12'
112119
: ''
113-
}
120+
} flexbox-col dc__gap-8`}
114121
>
115122
<Field {...props} />
116123
</div>

src/Common/RJSF/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,18 @@
1616

1717
import { ComponentProps } from 'react'
1818
import RJSFForm from '@rjsf/core'
19+
import { StrictRJSFSchema } from '@rjsf/utils'
1920

2021
export type FormProps = Omit<ComponentProps<typeof RJSFForm>, 'validator'>
22+
23+
interface Hidden {
24+
condition: boolean
25+
match: string
26+
}
27+
28+
export interface RJSFFormSchema extends StrictRJSFSchema {
29+
properties: {
30+
[key: string]: RJSFFormSchema
31+
}
32+
hidden: Hidden
33+
}

src/Common/RJSF/utils.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import { TranslatableString, englishStringTranslator } from '@rjsf/utils'
18-
import Tippy from '@tippyjs/react'
18+
import Tippy from '../Tippy'
1919

2020
/**
2121
* Override for the TranslatableString from RJSF
@@ -138,8 +138,16 @@ export const getInferredTypeFromValueType = (value) => {
138138
}
139139

140140
export const getTippyWrapperWithContent =
141-
(content, placement?: React.ComponentProps<typeof Tippy>['placement']) => (children) => (
142-
<Tippy className="default-tt dc__word-break" arrow={false} placement={placement || 'right'} content={content}>
141+
(content, placement?: React.ComponentProps<typeof Tippy>['placement'], truncateWidth = 0) =>
142+
(children) => (
143+
<Tippy
144+
className="default-tt"
145+
maxWidth={300}
146+
arrow={false}
147+
placement={placement || 'right'}
148+
content={content}
149+
truncateWidth={truncateWidth}
150+
>
143151
{children}
144152
</Tippy>
145153
)

0 commit comments

Comments
 (0)