Skip to content

Commit 63b47ff

Browse files
authored
Merge pull request #254 from PRO-Robotech/feature/CLOUD-100-release
[CLOUD-100]: VisibilityContainer criteria + hide on empty
2 parents 33b6fd9 + 5a22b68 commit 63b47ff

File tree

5 files changed

+304
-4
lines changed

5 files changed

+304
-4
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ yarn-error.log*
2424
storybook-static
2525

2626
.env.options
27+
28+
.yalc
29+
yalc.lock
30+
.yalc/**

package-lock.json

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

src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ const meta: Meta<TArgs> = {
3131
description:
3232
"data.value – template resolved via parseWithoutPartsOfUrl (e.g. \"{reqsJsonPath[0]['.data.flag']['-']}\")",
3333
},
34+
criteria: {
35+
control: { type: 'radio' },
36+
options: ['equals', 'notEquals', 'exists', 'notExists', null],
37+
mapping: {
38+
equals: 'equals',
39+
notEquals: 'notEquals',
40+
exists: 'exists',
41+
notExists: 'notExists',
42+
null: undefined,
43+
},
44+
description: 'data.criteria – optional comparison operator (equals / notEquals / exists / notExists)',
45+
},
46+
valueToCompare: {
47+
control: 'object',
48+
description: 'data.valueToCompare – string or array of strings to compare with resolved value',
49+
},
3450

3551
// provider knobs
3652
isLoading: {
@@ -56,6 +72,8 @@ const meta: Meta<TArgs> = {
5672
const data: TInner = {
5773
id: args.id,
5874
value: args.value,
75+
criteria: args.criteria as TInner['criteria'],
76+
valueToCompare: args.valueToCompare as TInner['valueToCompare'],
5977
}
6078

6179
return (
@@ -76,8 +94,15 @@ const meta: Meta<TArgs> = {
7694
fontSize: 13,
7795
}}
7896
>
79-
I am only visible when <code>value</code> resolves to something other than{' '}
80-
<code>~undefined-value~</code>.
97+
I am visible when:
98+
<ul style={{ marginTop: 6 }}>
99+
<li>
100+
<code>value</code> resolves (not <code>~undefined-value~</code>)
101+
</li>
102+
<li>
103+
If <code>criteria</code> is set, the comparison passes against <code>valueToCompare</code>
104+
</li>
105+
</ul>
81106
</div>
82107
</VisibilityContainer>
83108
</div>
@@ -115,6 +140,8 @@ export const Default: Story = {
115140
id: 'example-visibility-container',
116141
// typical template used by parseWithoutPartsOfUrl
117142
value: "{reqsJsonPath[0]['.data.flag']['-']}",
143+
criteria: undefined,
144+
valueToCompare: undefined,
118145

119146
// providers
120147
isLoading: false,
@@ -152,3 +179,219 @@ export const LoadingMultiQuery: Story = {
152179
isLoading: true,
153180
},
154181
}
182+
183+
export const CriteriaEqualsPasses: Story = {
184+
args: {
185+
...Default.args,
186+
id: 'visibility-criteria-equals-passes',
187+
criteria: 'equals',
188+
valueToCompare: ['show'],
189+
value: "{reqsJsonPath[0]['.data.flag']['-']}",
190+
multiQueryData: {
191+
req0: {
192+
data: {
193+
flag: 'show',
194+
},
195+
},
196+
},
197+
},
198+
}
199+
200+
export const CriteriaEqualsFails: Story = {
201+
args: {
202+
...Default.args,
203+
id: 'visibility-criteria-equals-fails',
204+
criteria: 'equals',
205+
valueToCompare: ['Role'],
206+
value: "{reqsJsonPath[0]['.data.flag']['-']}",
207+
multiQueryData: {
208+
req0: {
209+
data: {
210+
flag: 'ClusterRole',
211+
},
212+
},
213+
},
214+
},
215+
}
216+
217+
export const CriteriaNotEqualsPasses: Story = {
218+
args: {
219+
...Default.args,
220+
id: 'visibility-criteria-not-equals-passes',
221+
criteria: 'notEquals',
222+
valueToCompare: ['Role'],
223+
value: "{reqsJsonPath[0]['.data.flag']['-']}",
224+
multiQueryData: {
225+
req0: {
226+
data: {
227+
flag: 'ClusterRole',
228+
},
229+
},
230+
},
231+
},
232+
}
233+
234+
export const CriteriaNotEqualsFails: Story = {
235+
args: {
236+
...Default.args,
237+
id: 'visibility-criteria-not-equals-fails',
238+
criteria: 'notEquals',
239+
valueToCompare: ['Role'],
240+
value: "{reqsJsonPath[0]['.data.flag']['-']}",
241+
multiQueryData: {
242+
req0: {
243+
data: {
244+
flag: 'Role',
245+
},
246+
},
247+
},
248+
},
249+
}
250+
251+
// ========== NEW: EXISTS / NOT EXISTS CRITERIA ==========
252+
253+
export const CriteriaExistsPasses: Story = {
254+
args: {
255+
...Default.args,
256+
id: 'visibility-criteria-exists-passes',
257+
criteria: 'exists',
258+
value: "{reqsJsonPath[0]['.data.flag']['-']}",
259+
multiQueryData: {
260+
req0: {
261+
data: {
262+
flag: 'some-value',
263+
},
264+
},
265+
},
266+
},
267+
}
268+
269+
export const CriteriaExistsFails: Story = {
270+
args: {
271+
...Default.args,
272+
id: 'visibility-criteria-exists-fails',
273+
criteria: 'exists',
274+
value: "{reqsJsonPath[0]['.data.missing']['-']}",
275+
multiQueryData: {
276+
req0: {
277+
data: {
278+
flag: 'show',
279+
},
280+
},
281+
},
282+
},
283+
}
284+
285+
export const CriteriaNotExistsPasses: Story = {
286+
args: {
287+
...Default.args,
288+
id: 'visibility-criteria-not-exists-passes',
289+
criteria: 'notExists',
290+
value: "{reqsJsonPath[0]['.data.missing']['-']}",
291+
multiQueryData: {
292+
req0: {
293+
data: {
294+
flag: 'show',
295+
},
296+
},
297+
},
298+
},
299+
}
300+
301+
export const CriteriaNotExistsFails: Story = {
302+
args: {
303+
...Default.args,
304+
id: 'visibility-criteria-not-exists-fails',
305+
criteria: 'notExists',
306+
value: "{reqsJsonPath[0]['.data.flag']['-']}",
307+
multiQueryData: {
308+
req0: {
309+
data: {
310+
flag: 'some-value',
311+
},
312+
},
313+
},
314+
},
315+
}
316+
317+
// ========== NEW: DYNAMIC valueToCompare WITH parseAll ==========
318+
319+
export const DynamicValueToCompare: Story = {
320+
args: {
321+
...Default.args,
322+
id: 'visibility-dynamic-value-to-compare',
323+
criteria: 'equals',
324+
value: "{reqsJsonPath[0]['.status.phase']['-']}",
325+
// valueToCompare uses dynamic template from another request
326+
valueToCompare: ["{reqsJsonPath[1]['.data.expectedPhase']['-']}"],
327+
multiQueryData: {
328+
req0: {
329+
status: {
330+
phase: 'Running',
331+
},
332+
},
333+
req1: {
334+
data: {
335+
expectedPhase: 'Running',
336+
},
337+
},
338+
},
339+
},
340+
}
341+
342+
export const DynamicValueToCompareWithPartsOfUrl: Story = {
343+
args: {
344+
...Default.args,
345+
id: 'visibility-dynamic-value-to-compare-parts-of-url',
346+
criteria: 'equals',
347+
value: "{reqsJsonPath[0]['.metadata.namespace']['-']}",
348+
// valueToCompare uses URL part (e.g., {2} = cluster name from URL)
349+
valueToCompare: ['{2}'],
350+
partsOfUrl: ['', '', 'default', 'pods', 'my-pod'],
351+
multiQueryData: {
352+
req0: {
353+
metadata: {
354+
namespace: 'default',
355+
},
356+
},
357+
},
358+
},
359+
}
360+
361+
// ========== NEW: CHECKING AGAINST ~undefined-value~ ==========
362+
363+
export const CheckAgainstUndefinedValue: Story = {
364+
args: {
365+
...Default.args,
366+
id: 'visibility-check-undefined-value',
367+
criteria: 'equals',
368+
value: "{reqsJsonPath[0]['.metadata.deletionTimestamp']['-']}",
369+
valueToCompare: ['~undefined-value~'],
370+
multiQueryData: {
371+
req0: {
372+
metadata: {
373+
name: 'my-resource',
374+
// deletionTimestamp is missing (undefined)
375+
},
376+
},
377+
},
378+
},
379+
}
380+
381+
export const CheckNotUndefinedValue: Story = {
382+
args: {
383+
...Default.args,
384+
id: 'visibility-check-not-undefined-value',
385+
criteria: 'notEquals',
386+
value: "{reqsJsonPath[0]['.metadata.deletionTimestamp']['-']}",
387+
valueToCompare: ['~undefined-value~'],
388+
multiQueryData: {
389+
req0: {
390+
metadata: {
391+
name: 'my-resource',
392+
deletionTimestamp: '2024-01-01T00:00:00Z',
393+
},
394+
},
395+
},
396+
},
397+
}

src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import React, { FC } from 'react'
33
import { TDynamicComponentsAppTypeMap } from '../../types'
44
import { useMultiQuery } from '../../../DynamicRendererWithProviders/providers/hybridDataProvider'
5-
import { parseWithoutPartsOfUrl } from '../utils'
5+
import { usePartsOfUrl } from '../../../DynamicRendererWithProviders/providers/partsOfUrlContext'
6+
import { parseWithoutPartsOfUrl, parseAll } from '../utils'
67
import { Styled } from './styled'
78

89
export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['VisibilityContainer']; children?: any }> = ({
@@ -11,24 +12,57 @@ export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['Visib
1112
children,
1213
}) => {
1314
const { data: multiQueryData, isLoading: isMultiqueryLoading } = useMultiQuery()
15+
const partsOfUrl = usePartsOfUrl()
1416

1517
const {
1618
// eslint-disable-next-line @typescript-eslint/no-unused-vars
1719
id,
1820
value,
21+
criteria,
22+
valueToCompare,
1923
} = data
2024

25+
const replaceValues = partsOfUrl.partsOfUrl.reduce<Record<string, string | undefined>>((acc, value, index) => {
26+
acc[index.toString()] = value
27+
return acc
28+
}, {})
29+
2130
const valuePrepared = parseWithoutPartsOfUrl({
2231
text: value,
2332
multiQueryData,
2433
customFallback: '~undefined-value~',
2534
})
2635

36+
const shouldHideByCriteria = (() => {
37+
if (!criteria) return false
38+
39+
if (criteria === 'exists') {
40+
return !valuePrepared || valuePrepared === '~undefined-value~'
41+
}
42+
if (criteria === 'notExists') {
43+
return !!valuePrepared && valuePrepared !== '~undefined-value~'
44+
}
45+
46+
if (!valueToCompare) return false
47+
48+
const targets = Array.isArray(valueToCompare)
49+
? valueToCompare.map(target => parseAll({ text: target, replaceValues, multiQueryData }))
50+
: [parseAll({ text: String(valueToCompare), replaceValues, multiQueryData })]
51+
52+
const matches = targets.includes(String(valuePrepared))
53+
54+
if (criteria === 'equals') return !matches
55+
if (criteria === 'notEquals') return matches
56+
return false
57+
})()
58+
2759
if (isMultiqueryLoading) {
2860
return <div>Loading multiquery</div>
2961
}
3062

63+
const shouldAutoHide = !criteria && (!valuePrepared || valuePrepared === '~undefined-value~')
64+
3165
return (
32-
<Styled.VisibilityContainer $hidden={valuePrepared === '~undefined-value~'}>{children}</Styled.VisibilityContainer>
66+
<Styled.VisibilityContainer $hidden={shouldAutoHide || shouldHideByCriteria}>{children}</Styled.VisibilityContainer>
3367
)
3468
}

src/components/organisms/DynamicComponents/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ export type TDynamicComponentsAppTypeMap = {
132132
VisibilityContainer: {
133133
id: number | string
134134
value: string
135+
criteria?: 'equals' | 'notEquals' | 'exists' | 'notExists'
136+
valueToCompare?: string | string[]
135137
}
136138
ArrayOfObjectsToKeyValues: {
137139
id: number | string

0 commit comments

Comments
 (0)