From bc87ea4e631b8e497bd866364d2a6093321b096a Mon Sep 17 00:00:00 2001 From: vsmeyukha Date: Thu, 11 Dec 2025 21:46:57 +0300 Subject: [PATCH 1/2] CLOUD-100: fix VisibilityContainer logic --- .gitignore | 4 + package-lock.json | 17 ++++ .../VisibilityContainer.stories.tsx | 93 ++++++++++++++++++- .../VisibilityContainer.tsx | 19 +++- .../organisms/DynamicComponents/types.ts | 2 + 5 files changed, 132 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 4a447c61..d76d3577 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ yarn-error.log* storybook-static .env.options + +.yalc +yalc.lock +.yalc/** diff --git a/package-lock.json b/package-lock.json index b19a888d..6d1dfbdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8949,6 +8949,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -15497,6 +15498,22 @@ } } }, + "node_modules/vite-tsconfig-paths/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/vm-browserify": { "version": "1.1.2", "dev": true, diff --git a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx index 2f210cf9..579c428b 100644 --- a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx +++ b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx @@ -31,6 +31,16 @@ const meta: Meta = { description: "data.value – template resolved via parseWithoutPartsOfUrl (e.g. \"{reqsJsonPath[0]['.data.flag']['-']}\")", }, + criteria: { + control: { type: 'radio' }, + options: ['equals', 'notEquals', null], + mapping: { equals: 'equals', notEquals: 'notEquals', null: undefined }, + description: 'data.criteria – optional comparison operator (equals / notEquals)', + }, + valueToCompare: { + control: 'object', + description: 'data.valueToCompare – string or array of strings to compare with resolved value', + }, // provider knobs isLoading: { @@ -56,6 +66,8 @@ const meta: Meta = { const data: TInner = { id: args.id, value: args.value, + criteria: args.criteria as TInner['criteria'], + valueToCompare: args.valueToCompare as TInner['valueToCompare'], } return ( @@ -76,8 +88,15 @@ const meta: Meta = { fontSize: 13, }} > - I am only visible when value resolves to something other than{' '} - ~undefined-value~. + I am visible when: +
    +
  • + value resolves (not ~undefined-value~) +
  • +
  • + If criteria is set, the comparison passes against valueToCompare +
  • +
@@ -115,6 +134,8 @@ export const Default: Story = { id: 'example-visibility-container', // typical template used by parseWithoutPartsOfUrl value: "{reqsJsonPath[0]['.data.flag']['-']}", + criteria: undefined, + valueToCompare: undefined, // providers isLoading: false, @@ -152,3 +173,71 @@ export const LoadingMultiQuery: Story = { isLoading: true, }, } + +export const CriteriaEqualsPasses: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-equals-passes', + criteria: 'equals', + valueToCompare: ['show'], + value: "{reqsJsonPath[0]['.data.flag']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'show', + }, + }, + }, + }, +} + +export const CriteriaEqualsFails: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-equals-fails', + criteria: 'equals', + valueToCompare: ['Role'], + value: "{reqsJsonPath[0]['.data.flag']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'ClusterRole', + }, + }, + }, + }, +} + +export const CriteriaNotEqualsPasses: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-not-equals-passes', + criteria: 'notEquals', + valueToCompare: ['Role'], + value: "{reqsJsonPath[0]['.data.flag']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'ClusterRole', + }, + }, + }, + }, +} + +export const CriteriaNotEqualsFails: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-not-equals-fails', + criteria: 'notEquals', + valueToCompare: ['Role'], + value: "{reqsJsonPath[0]['.data.flag']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'Role', + }, + }, + }, + }, +} diff --git a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx index 10789d2b..5d132dcb 100644 --- a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx +++ b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx @@ -16,6 +16,8 @@ export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['Visib // eslint-disable-next-line @typescript-eslint/no-unused-vars id, value, + criteria, + valueToCompare, } = data const valuePrepared = parseWithoutPartsOfUrl({ @@ -24,11 +26,26 @@ export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['Visib customFallback: '~undefined-value~', }) + const shouldHideByCriteria = (() => { + if (!criteria || !valueToCompare) return false + + const targets = Array.isArray(valueToCompare) ? valueToCompare.map(String) : [String(valueToCompare)] + const matches = targets.includes(String(valuePrepared)) + + if (criteria === 'equals') return !matches + if (criteria === 'notEquals') return matches + return false + })() + if (isMultiqueryLoading) { return
Loading multiquery
} return ( - {children} + + {children} + ) } diff --git a/src/components/organisms/DynamicComponents/types.ts b/src/components/organisms/DynamicComponents/types.ts index 8d2bc33f..0ddc0203 100644 --- a/src/components/organisms/DynamicComponents/types.ts +++ b/src/components/organisms/DynamicComponents/types.ts @@ -132,6 +132,8 @@ export type TDynamicComponentsAppTypeMap = { VisibilityContainer: { id: number | string value: string + criteria?: 'equals' | 'notEquals' + valueToCompare?: string | string[] } ArrayOfObjectsToKeyValues: { id: number | string From 5a22b68eb31645fa584af4704e9e9a0532ab25f6 Mon Sep 17 00:00:00 2001 From: vsmeyukha Date: Fri, 12 Dec 2025 18:13:14 +0300 Subject: [PATCH 2/2] added parseAll & exists criteria --- .../VisibilityContainer.stories.tsx | 160 +++++++++++++++++- .../VisibilityContainer.tsx | 33 +++- .../organisms/DynamicComponents/types.ts | 2 +- 3 files changed, 183 insertions(+), 12 deletions(-) diff --git a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx index 579c428b..43f985b6 100644 --- a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx +++ b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.stories.tsx @@ -33,9 +33,15 @@ const meta: Meta = { }, criteria: { control: { type: 'radio' }, - options: ['equals', 'notEquals', null], - mapping: { equals: 'equals', notEquals: 'notEquals', null: undefined }, - description: 'data.criteria – optional comparison operator (equals / notEquals)', + options: ['equals', 'notEquals', 'exists', 'notExists', null], + mapping: { + equals: 'equals', + notEquals: 'notEquals', + exists: 'exists', + notExists: 'notExists', + null: undefined, + }, + description: 'data.criteria – optional comparison operator (equals / notEquals / exists / notExists)', }, valueToCompare: { control: 'object', @@ -241,3 +247,151 @@ export const CriteriaNotEqualsFails: Story = { }, }, } + +// ========== NEW: EXISTS / NOT EXISTS CRITERIA ========== + +export const CriteriaExistsPasses: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-exists-passes', + criteria: 'exists', + value: "{reqsJsonPath[0]['.data.flag']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'some-value', + }, + }, + }, + }, +} + +export const CriteriaExistsFails: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-exists-fails', + criteria: 'exists', + value: "{reqsJsonPath[0]['.data.missing']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'show', + }, + }, + }, + }, +} + +export const CriteriaNotExistsPasses: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-not-exists-passes', + criteria: 'notExists', + value: "{reqsJsonPath[0]['.data.missing']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'show', + }, + }, + }, + }, +} + +export const CriteriaNotExistsFails: Story = { + args: { + ...Default.args, + id: 'visibility-criteria-not-exists-fails', + criteria: 'notExists', + value: "{reqsJsonPath[0]['.data.flag']['-']}", + multiQueryData: { + req0: { + data: { + flag: 'some-value', + }, + }, + }, + }, +} + +// ========== NEW: DYNAMIC valueToCompare WITH parseAll ========== + +export const DynamicValueToCompare: Story = { + args: { + ...Default.args, + id: 'visibility-dynamic-value-to-compare', + criteria: 'equals', + value: "{reqsJsonPath[0]['.status.phase']['-']}", + // valueToCompare uses dynamic template from another request + valueToCompare: ["{reqsJsonPath[1]['.data.expectedPhase']['-']}"], + multiQueryData: { + req0: { + status: { + phase: 'Running', + }, + }, + req1: { + data: { + expectedPhase: 'Running', + }, + }, + }, + }, +} + +export const DynamicValueToCompareWithPartsOfUrl: Story = { + args: { + ...Default.args, + id: 'visibility-dynamic-value-to-compare-parts-of-url', + criteria: 'equals', + value: "{reqsJsonPath[0]['.metadata.namespace']['-']}", + // valueToCompare uses URL part (e.g., {2} = cluster name from URL) + valueToCompare: ['{2}'], + partsOfUrl: ['', '', 'default', 'pods', 'my-pod'], + multiQueryData: { + req0: { + metadata: { + namespace: 'default', + }, + }, + }, + }, +} + +// ========== NEW: CHECKING AGAINST ~undefined-value~ ========== + +export const CheckAgainstUndefinedValue: Story = { + args: { + ...Default.args, + id: 'visibility-check-undefined-value', + criteria: 'equals', + value: "{reqsJsonPath[0]['.metadata.deletionTimestamp']['-']}", + valueToCompare: ['~undefined-value~'], + multiQueryData: { + req0: { + metadata: { + name: 'my-resource', + // deletionTimestamp is missing (undefined) + }, + }, + }, + }, +} + +export const CheckNotUndefinedValue: Story = { + args: { + ...Default.args, + id: 'visibility-check-not-undefined-value', + criteria: 'notEquals', + value: "{reqsJsonPath[0]['.metadata.deletionTimestamp']['-']}", + valueToCompare: ['~undefined-value~'], + multiQueryData: { + req0: { + metadata: { + name: 'my-resource', + deletionTimestamp: '2024-01-01T00:00:00Z', + }, + }, + }, + }, +} diff --git a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx index 5d132dcb..06bfa684 100644 --- a/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx +++ b/src/components/organisms/DynamicComponents/molecules/VisibilityContainer/VisibilityContainer.tsx @@ -2,7 +2,8 @@ import React, { FC } from 'react' import { TDynamicComponentsAppTypeMap } from '../../types' import { useMultiQuery } from '../../../DynamicRendererWithProviders/providers/hybridDataProvider' -import { parseWithoutPartsOfUrl } from '../utils' +import { usePartsOfUrl } from '../../../DynamicRendererWithProviders/providers/partsOfUrlContext' +import { parseWithoutPartsOfUrl, parseAll } from '../utils' import { Styled } from './styled' export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['VisibilityContainer']; children?: any }> = ({ @@ -11,6 +12,7 @@ export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['Visib children, }) => { const { data: multiQueryData, isLoading: isMultiqueryLoading } = useMultiQuery() + const partsOfUrl = usePartsOfUrl() const { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -20,6 +22,11 @@ export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['Visib valueToCompare, } = data + const replaceValues = partsOfUrl.partsOfUrl.reduce>((acc, value, index) => { + acc[index.toString()] = value + return acc + }, {}) + const valuePrepared = parseWithoutPartsOfUrl({ text: value, multiQueryData, @@ -27,9 +34,21 @@ export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['Visib }) const shouldHideByCriteria = (() => { - if (!criteria || !valueToCompare) return false + if (!criteria) return false + + if (criteria === 'exists') { + return !valuePrepared || valuePrepared === '~undefined-value~' + } + if (criteria === 'notExists') { + return !!valuePrepared && valuePrepared !== '~undefined-value~' + } + + if (!valueToCompare) return false + + const targets = Array.isArray(valueToCompare) + ? valueToCompare.map(target => parseAll({ text: target, replaceValues, multiQueryData })) + : [parseAll({ text: String(valueToCompare), replaceValues, multiQueryData })] - const targets = Array.isArray(valueToCompare) ? valueToCompare.map(String) : [String(valueToCompare)] const matches = targets.includes(String(valuePrepared)) if (criteria === 'equals') return !matches @@ -41,11 +60,9 @@ export const VisibilityContainer: FC<{ data: TDynamicComponentsAppTypeMap['Visib return
Loading multiquery
} + const shouldAutoHide = !criteria && (!valuePrepared || valuePrepared === '~undefined-value~') + return ( - - {children} - + {children} ) } diff --git a/src/components/organisms/DynamicComponents/types.ts b/src/components/organisms/DynamicComponents/types.ts index 0ddc0203..18cd664a 100644 --- a/src/components/organisms/DynamicComponents/types.ts +++ b/src/components/organisms/DynamicComponents/types.ts @@ -132,7 +132,7 @@ export type TDynamicComponentsAppTypeMap = { VisibilityContainer: { id: number | string value: string - criteria?: 'equals' | 'notEquals' + criteria?: 'equals' | 'notEquals' | 'exists' | 'notExists' valueToCompare?: string | string[] } ArrayOfObjectsToKeyValues: {