Skip to content

Commit 87d15ad

Browse files
authored
Merge pull request #295 from devtron-labs/feat/use-rjsf-chart-store
feat: handle the various hidden objects possible in charts of chart store
2 parents 668fd65 + 0ce87fc commit 87d15ad

File tree

11 files changed

+135
-44
lines changed

11 files changed

+135
-44
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "0.3.9",
3+
"version": "0.3.10",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",

src/Common/Helper.tsx

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,24 @@ import * as Sentry from '@sentry/browser'
2222
import moment from 'moment'
2323
import { useLocation } from 'react-router-dom'
2424
import YAML from 'yaml'
25-
import { ERROR_EMPTY_SCREEN, SortingOrder, EXCLUDED_FALSY_VALUES, DISCORD_LINK, ZERO_TIME_STRING, TOAST_ACCESS_DENIED } from './Constants'
25+
import { deepEquals } from '@rjsf/utils'
26+
import {
27+
ERROR_EMPTY_SCREEN,
28+
SortingOrder,
29+
EXCLUDED_FALSY_VALUES,
30+
DISCORD_LINK,
31+
ZERO_TIME_STRING,
32+
TOAST_ACCESS_DENIED,
33+
} from './Constants'
2634
import { ServerErrors } from './ServerError'
2735
import { AsyncOptions, AsyncState, UseSearchString } from './Types'
28-
import { scrollableInterface, DATE_TIME_FORMAT_STRING, ToastManager, ToastVariantType } from '../Shared'
36+
import {
37+
scrollableInterface,
38+
DATE_TIME_FORMAT_STRING,
39+
ToastManager,
40+
ToastVariantType,
41+
versionComparatorBySortOrder,
42+
} from '../Shared'
2943
import { ReactComponent as ArrowDown } from '../Assets/Icon/ic-chevron-down.svg'
3044

3145
export function showError(serverError, showToastOnUnknownError = true, hideAccessError = false) {
@@ -620,6 +634,7 @@ export const getFilteredChartVersions = (charts, selectedChartType) =>
620634
// Filter chart versions based on selected chart type
621635
charts
622636
.filter((item) => item?.chartType === selectedChartType.value)
637+
.sort((a, b) => versionComparatorBySortOrder(a?.chartVersion, b?.chartVersion))
623638
.map((item) => ({
624639
value: item?.chartVersion,
625640
label: item?.chartVersion,
@@ -818,28 +833,7 @@ export const compareObjectLength = (objA: any, objB: any): boolean => {
818833
* Return deep copy of the object
819834
*/
820835
export function deepEqual(configA: any, configB: any): boolean {
821-
try {
822-
if (configA === configB) {
823-
return true
824-
}
825-
if ((configA && !configB) || (!configA && configB) || !compareObjectLength(configA, configB)) {
826-
return false
827-
}
828-
let isEqual = true
829-
for (const idx in configA) {
830-
if (!isEqual) {
831-
break
832-
} else if (typeof configA[idx] === 'object' && typeof configB[idx] === 'object') {
833-
isEqual = deepEqual(configA[idx], configB[idx])
834-
} else if (configA[idx] !== configB[idx]) {
835-
isEqual = false
836-
}
837-
}
838-
return isEqual
839-
} catch (err) {
840-
showError(err)
841-
return true
842-
}
836+
return deepEquals(configA, configB)
843837
}
844838

845839
export function shallowEqual(objA, objB) {

src/Common/RJSF/Form.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const RJSFForm = (props: FormProps) => (
3939
...templates,
4040
...props.templates,
4141
}}
42+
formContext={props.formData}
4243
widgets={{ ...widgets, ...props.widgets }}
4344
translateString={translateString}
4445
/>

src/Common/RJSF/templates/ObjectFieldTemplate.tsx

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

17-
import { ObjectFieldTemplateProps, canExpand, titleId } from '@rjsf/utils'
17+
import { ObjectFieldTemplateProps, canExpand, titleId, deepEquals } from '@rjsf/utils'
1818
import { JSONPath } from 'jsonpath-plus'
19+
import { convertJSONPointerToJSONPath } from '@Common/Helper'
1920
import { FieldRowWithLabel } from '../common/FieldRow'
2021
import { TitleField } from './TitleField'
2122
import { AddButton } from './ButtonTemplates'
2223
import { RJSFFormSchema } from '../types'
24+
import { parseSchemaHiddenType } from '../utils'
2325

2426
const Field = ({
2527
disabled,
@@ -33,6 +35,7 @@ const Field = ({
3335
schema,
3436
title,
3537
uiSchema,
38+
formContext,
3639
}: ObjectFieldTemplateProps<any, RJSFFormSchema, any>) => {
3740
const hasAdditionalProperties = !!schema.additionalProperties
3841

@@ -54,8 +57,19 @@ const Field = ({
5457
return true
5558
}
5659
try {
57-
const value = JSONPath({ path: hiddenSchemaProp.match, json: formData })?.[0]
58-
const isHidden = value === undefined || hiddenSchemaProp.condition === value
60+
const hiddenSchema = parseSchemaHiddenType(hiddenSchemaProp)
61+
if (!hiddenSchema.path) {
62+
throw new Error('Empty path property of hidden descriptor field')
63+
}
64+
if (!hiddenSchema.path.match(/^\/\w+(\/\w+)*$/g)) {
65+
throw new Error('Provided path is not a valid JSON pointer')
66+
}
67+
// NOTE: formContext is the formData passed to RJSFForm
68+
const value = JSONPath({
69+
path: convertJSONPointerToJSONPath(hiddenSchema.path),
70+
json: formContext,
71+
})?.[0]
72+
const isHidden = value === undefined || deepEquals(hiddenSchema.value, value)
5973
return !isHidden
6074
} catch {
6175
return true

src/Common/RJSF/types.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,22 @@ import { StrictRJSFSchema } from '@rjsf/utils'
2020

2121
export type FormProps = Omit<ComponentProps<typeof RJSFForm>, 'validator'>
2222

23-
interface Hidden {
24-
condition: boolean
25-
match: string
23+
export interface MetaHiddenType {
24+
value: any
25+
path: string
2626
}
2727

28+
export type HiddenType =
29+
| MetaHiddenType
30+
| {
31+
condition: any
32+
value: string
33+
}
34+
| string
35+
2836
export interface RJSFFormSchema extends StrictRJSFSchema {
2937
properties: {
3038
[key: string]: RJSFFormSchema
3139
}
32-
hidden: Hidden
40+
hidden: HiddenType
3341
}

src/Common/RJSF/utils.tsx

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

1717
import { TranslatableString, englishStringTranslator } from '@rjsf/utils'
18+
import { HiddenType, MetaHiddenType } from './types'
1819

1920
/**
2021
* Override for the TranslatableString from RJSF
@@ -135,3 +136,59 @@ export const getInferredTypeFromValueType = (value) => {
135136
return 'null'
136137
}
137138
}
139+
140+
const conformPathToPointers = (path: string): string => {
141+
if (!path) {
142+
return ''
143+
}
144+
const trimmedPath = path.trim()
145+
const isSlashSeparatedPathMissingBeginSlash = trimmedPath.match(/^\w+(\/\w+)*$/g)
146+
if (isSlashSeparatedPathMissingBeginSlash) {
147+
return `/${trimmedPath}`
148+
}
149+
const isDotSeparatedPath = trimmedPath.match(/^\w+(\.\w+)*$/g)
150+
if (isDotSeparatedPath) {
151+
// NOTE: replacing dots with forward slash (/)
152+
return `/${trimmedPath.replaceAll(/\./g, '/')}`
153+
}
154+
return trimmedPath
155+
}
156+
157+
const emptyMetaHiddenTypeInstance: MetaHiddenType = {
158+
value: false,
159+
path: '',
160+
}
161+
162+
export const parseSchemaHiddenType = (hiddenSchema: HiddenType): MetaHiddenType => {
163+
if (!hiddenSchema) {
164+
return null
165+
}
166+
const clone = structuredClone(hiddenSchema)
167+
if (typeof clone === 'string') {
168+
return {
169+
value: false,
170+
path: conformPathToPointers(clone),
171+
}
172+
}
173+
if (typeof clone !== 'object') {
174+
return structuredClone(emptyMetaHiddenTypeInstance)
175+
}
176+
if (
177+
Object.hasOwn(clone, 'condition') &&
178+
'condition' in clone &&
179+
Object.hasOwn(clone, 'value') &&
180+
'value' in clone
181+
) {
182+
return {
183+
value: clone.condition,
184+
path: conformPathToPointers(clone.value),
185+
}
186+
}
187+
if (Object.hasOwn(clone, 'value') && 'value' in clone && Object.hasOwn(clone, 'path') && 'path' in clone) {
188+
return {
189+
value: clone.value,
190+
path: conformPathToPointers(clone.path),
191+
}
192+
}
193+
return structuredClone(emptyMetaHiddenTypeInstance)
194+
}

src/Common/Tooltip/constants.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const TOOLTIP_CONTENTS = {
2+
INVALID_INPUT: 'Valid input is required for all mandatory fields.',
3+
}

src/Common/Tooltip/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { default as Tooltip } from './Tooltip'
2+
export { TOOLTIP_CONTENTS } from './constants'

src/Pages/GlobalConfigurations/DeploymentCharts/utils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { versionComparatorBySortOrder } from '@Shared/Helpers'
2-
import { SortingOrder } from '@Common/Constants'
32
import { DeploymentChartListDTO, DeploymentChartType, DEVTRON_DEPLOYMENT_CHART_NAMES } from './types'
43
import fallbackGuiSchema from './basicViewSchema.json'
54

@@ -31,7 +30,7 @@ export const convertDeploymentChartListToChartType = (data: DeploymentChartListD
3130
{} as Record<string, DeploymentChartType>,
3231
)
3332
const result = Object.values(chartMap).map((element) => {
34-
element.versions.sort((a, b) => versionComparatorBySortOrder(a, b, 'version', SortingOrder.DESC))
33+
element.versions.sort((a, b) => versionComparatorBySortOrder(a.version, b.version))
3534
return element
3635
})
3736
return result

src/Shared/Helpers.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
UserApprovalConfigType,
3030
PATTERNS,
3131
ZERO_TIME_STRING,
32+
noop,
3233
} from '../Common'
3334
import {
3435
AggregationKeys,
@@ -138,17 +139,12 @@ export const numberComparatorBySortOrder = (
138139
sortOrder: SortingOrder = SortingOrder.ASC,
139140
): number => (sortOrder === SortingOrder.ASC ? a - b : b - a)
140141

141-
export function versionComparatorBySortOrder(
142-
a: Record<string, any>,
143-
b: Record<string, any>,
144-
compareKey: string,
145-
orderBy: SortingOrder,
146-
) {
142+
export function versionComparatorBySortOrder(a: string, b: string, orderBy = SortingOrder.ASC) {
147143
if (orderBy === SortingOrder.DESC) {
148-
return b[compareKey].localeCompare(a[compareKey], undefined, { numeric: true })
144+
return a?.localeCompare(b, undefined, { numeric: true }) ?? 1
149145
}
150146

151-
return a[compareKey].localeCompare(b[compareKey], undefined, { numeric: true })
147+
return b?.localeCompare(a, undefined, { numeric: true }) ?? 1
152148
}
153149

154150
export const getWebhookEventIcon = (eventName: WebhookEventNameType) => {
@@ -807,3 +803,21 @@ export const getIsManualApprovalSpecific = (userApprovalConfig?: Pick<UserApprov
807803
export const getHandleOpenURL = (url: string) => () => {
808804
window.open(url, '_blank', 'noreferrer')
809805
}
806+
807+
export const getDefaultValueFromType = (value: unknown) => {
808+
switch (typeof value) {
809+
case 'number':
810+
return 0
811+
case 'string':
812+
return ''
813+
case 'object':
814+
if (value === null) {
815+
return null
816+
}
817+
return Array.isArray(value) ? [] : {}
818+
case 'function':
819+
return noop
820+
default:
821+
return null
822+
}
823+
}

0 commit comments

Comments
 (0)