Repository: apache/airflow
File: airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldDropdown.tsx (TypeScript)

Code before:
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import { createListCollection } from "@chakra-ui/react/collection";
import { useRef } from "react";
import { useTranslation } from "react-i18next";

import { Select } from "src/components/ui";
import { paramPlaceholder, useParamStore } from "src/queries/useParamStore";

import type { FlexibleFormElementProps } from ".";

const labelLookup = (key: number | string | null, valuesDisplay: Record<string, string> | undefined): string => {
  // Convert null to string to avoid issues with object key lookup
  const stringKey = key === null ? "null" : String(key);

  if (valuesDisplay && typeof valuesDisplay === "object") {
    return valuesDisplay[stringKey] ?? stringKey;
  }

  return stringKey;
};
const enumTypes = ["string", "number", "integer"];

export const FieldDropdown = ({ name, namespace = "default", onUpdate }: FlexibleFormElementProps) => {
  const { t: translate } = useTranslation("components");
  const { disabled, paramsDict, setParamsDict } = useParamStore(namespace);
  const param = paramsDict[name] ?? paramPlaceholder;

  const selectOptions = createListCollection({
    items:
      param.schema.enum?.map((value) => {
        // Convert null to string "null" for zag-js compatibility
        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/no-unnecessary-condition
        const stringValue = String(value === null ? "null" : value);

        return {
          label: labelLookup(value, param.schema.values_display),
          value: stringValue,
        };

Review comment (bug):
I think the replacement is a good idea such that the used library does not fail. But in my view the literal `null` looks a bit dangerous as I assume it might be really used as a value / string.

Would it be possible to rather switch to some exotic string with less potential for clash like `__null__` or `__#null#__`? Woul dbe good to define this as constant that it is used consistently in code.

Code after:
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import { createListCollection } from "@chakra-ui/react/collection";
import { useRef } from "react";
import { useTranslation } from "react-i18next";

import { Select } from "src/components/ui";
import { paramPlaceholder, useParamStore } from "src/queries/useParamStore";

import type { FlexibleFormElementProps } from ".";

const NULL_STRING_VALUE = "__null__";

const labelLookup = (
  key: boolean | number | string | null,
  valuesDisplay: Record<string, string> | undefined,
): string => {
  if (valuesDisplay && typeof valuesDisplay === "object") {
    const stringKey = key === null ? "null" : String(key);

    return valuesDisplay[stringKey] ?? valuesDisplay.None ?? stringKey;
  }

  return key === null ? "null" : String(key);
};
const enumTypes = ["string", "number", "integer"];

export const FieldDropdown = ({ name, namespace = "default", onUpdate }: FlexibleFormElementProps) => {
  const { t: translate } = useTranslation("components");
  const { disabled, paramsDict, setParamsDict } = useParamStore(namespace);
  const param = paramsDict[name] ?? paramPlaceholder;

  const selectOptions = createListCollection({
    items:
      param.schema.enum?.map((value) => {
        // Convert null to string constant for zag-js compatibility
        const stringValue = String(value ?? NULL_STRING_VALUE);

        return {