export function dbReducer()

in superset-frontend/src/features/databases/DatabaseModal/index.tsx [251:554]


export function dbReducer(
  state: Partial<DatabaseObject> | null,
  action: DBReducerActionType,
): Partial<DatabaseObject> | null {
  const trimmedState = {
    ...(state || {}),
  };
  let query = {};
  let query_input = '';
  let parametersCatalog;
  let actionPayloadJson;
  const extraJson: ExtraJson = JSON.parse(trimmedState.extra || '{}');

  switch (action.type) {
    case ActionType.ExtraEditorChange:
      // "extra" payload in state is a string
      try {
        // we don't want to stringify encoded strings twice
        actionPayloadJson = JSON.parse(action.payload.json || '{}');
      } catch (e) {
        actionPayloadJson = action.payload.json;
      }
      return {
        ...trimmedState,
        extra: JSON.stringify({
          ...extraJson,
          [action.payload.name]: actionPayloadJson,
        }),
      };
    case ActionType.EncryptedExtraInputChange:
      return {
        ...trimmedState,
        masked_encrypted_extra: JSON.stringify({
          ...JSON.parse(trimmedState.masked_encrypted_extra || '{}'),
          [action.payload.name]: action.payload.value,
        }),
      };
    case ActionType.ExtraInputChange:
      // "extra" payload in state is a string
      if (
        action.payload.name === 'schema_cache_timeout' ||
        action.payload.name === 'table_cache_timeout'
      ) {
        return {
          ...trimmedState,
          extra: JSON.stringify({
            ...extraJson,
            metadata_cache_timeout: {
              ...extraJson?.metadata_cache_timeout,
              [action.payload.name]: action.payload.value,
            },
          }),
        };
      }
      if (action.payload.name === 'schemas_allowed_for_file_upload') {
        return {
          ...trimmedState,
          extra: JSON.stringify({
            ...extraJson,
            schemas_allowed_for_file_upload: (action.payload.value || '')
              .split(',')
              .filter(schema => schema !== ''),
          }),
        };
      }
      if (action.payload.name === 'http_path') {
        return {
          ...trimmedState,
          extra: JSON.stringify({
            ...extraJson,
            engine_params: {
              connect_args: {
                [action.payload.name]: action.payload.value?.trim(),
              },
            },
          }),
        };
      }
      if (action.payload.name === 'expand_rows') {
        return {
          ...trimmedState,
          extra: JSON.stringify({
            ...extraJson,
            schema_options: {
              ...extraJson?.schema_options,
              [action.payload.name]: !!action.payload.value,
            },
          }),
        };
      }
      return {
        ...trimmedState,
        extra: JSON.stringify({
          ...extraJson,
          [action.payload.name]:
            action.payload.type === 'checkbox'
              ? action.payload.checked
              : action.payload.value,
        }),
      };
    case ActionType.InputChange:
      if (action.payload.type === 'checkbox') {
        return {
          ...trimmedState,
          [action.payload.name]: action.payload.checked,
        };
      }
      return {
        ...trimmedState,
        [action.payload.name]: action.payload.value,
      };
    case ActionType.ParametersChange:
      // catalog params will always have a catalog state for
      // dbs that use a catalog, i.e., gsheets, even if the
      // fields are empty strings
      if (
        action.payload.type?.startsWith('catalog') &&
        trimmedState.catalog !== undefined
      ) {
        // Formatting wrapping google sheets table catalog
        const catalogCopy: CatalogObject[] = [...trimmedState.catalog];
        const idx = action.payload.type?.split('-')[1];
        const catalogToUpdate: CatalogObject =
          catalogCopy[parseInt(idx, 10)] || {};
        if (action.payload.value !== undefined) {
          catalogToUpdate[action.payload.name as keyof CatalogObject] =
            action.payload.value;
        }

        // insert updated catalog to existing state
        catalogCopy.splice(parseInt(idx, 10), 1, catalogToUpdate);

        // format catalog for state
        // eslint-disable-next-line array-callback-return
        parametersCatalog = catalogCopy.reduce<Record<string, string>>(
          (obj, item: CatalogObject) => {
            const catalog = { ...obj };
            catalog[item.name as keyof CatalogObject] = item.value;
            return catalog;
          },
          {},
        );

        return {
          ...trimmedState,
          catalog: catalogCopy,
          parameters: {
            ...trimmedState.parameters,
            catalog: parametersCatalog,
          },
        };
      }
      return {
        ...trimmedState,
        parameters: {
          ...trimmedState.parameters,
          [action.payload.name]: action.payload.value,
        },
      };

    case ActionType.ParametersSSHTunnelChange:
      return {
        ...trimmedState,
        ssh_tunnel: {
          ...trimmedState.ssh_tunnel,
          [action.payload.name]: action.payload.value,
        },
      };
    case ActionType.SetSSHTunnelLoginMethod: {
      let ssh_tunnel = {};
      if (trimmedState?.ssh_tunnel) {
        // remove any attributes that are considered sensitive
        ssh_tunnel = pick(trimmedState.ssh_tunnel, [
          'id',
          'server_address',
          'server_port',
          'username',
        ]);
      }
      if (action.payload.login_method === AuthType.PrivateKey) {
        return {
          ...trimmedState,
          ssh_tunnel: {
            private_key: trimmedState?.ssh_tunnel?.private_key,
            private_key_password:
              trimmedState?.ssh_tunnel?.private_key_password,
            ...ssh_tunnel,
          },
        };
      }
      if (action.payload.login_method === AuthType.Password) {
        return {
          ...trimmedState,
          ssh_tunnel: {
            password: trimmedState?.ssh_tunnel?.password,
            ...ssh_tunnel,
          },
        };
      }
      return {
        ...trimmedState,
      };
    }
    case ActionType.RemoveSSHTunnelConfig:
      return {
        ...trimmedState,
        ssh_tunnel: undefined,
      };
    case ActionType.AddTableCatalogSheet:
      if (trimmedState.catalog !== undefined) {
        return {
          ...trimmedState,
          catalog: [...trimmedState.catalog, { name: '', value: '' }],
        };
      }
      return {
        ...trimmedState,
        catalog: [{ name: '', value: '' }],
      };
    case ActionType.RemoveTableCatalogSheet:
      trimmedState.catalog?.splice(action.payload.indexToDelete, 1);
      return {
        ...trimmedState,
      };
    case ActionType.EditorChange:
      return {
        ...trimmedState,
        [action.payload.name]: action.payload.json,
      };
    case ActionType.QueryChange:
      return {
        ...trimmedState,
        parameters: {
          ...trimmedState.parameters,
          query: Object.fromEntries(new URLSearchParams(action.payload.value)),
        },
        query_input: action.payload.value,
      };
    case ActionType.TextChange:
      return {
        ...trimmedState,
        [action.payload.name]: action.payload.value,
      };
    case ActionType.Fetched:
      // convert query to a string and store in query_input
      query = action.payload?.parameters?.query || {};
      query_input = Object.entries(query)
        .map(([key, value]) => `${key}=${value}`)
        .join('&');

      if (
        action.payload.masked_encrypted_extra &&
        action.payload.configuration_method === ConfigurationMethod.DynamicForm
      ) {
        // "extra" payload from the api is a string
        const extraJsonPayload: ExtraJson = {
          ...JSON.parse((action.payload.extra as string) || '{}'),
        };

        const payloadCatalog = extraJsonPayload.engine_params?.catalog;

        const engineRootCatalog = Object.entries(payloadCatalog || {}).map(
          ([name, value]: string[]) => ({ name, value }),
        );

        return {
          ...action.payload,
          engine: action.payload.backend || trimmedState.engine,
          configuration_method: action.payload.configuration_method,
          catalog: engineRootCatalog,
          parameters: {
            ...(action.payload.parameters || trimmedState.parameters),
            catalog: payloadCatalog,
          },
          query_input,
        };
      }
      return {
        ...action.payload,
        masked_encrypted_extra: action.payload.masked_encrypted_extra || '',
        engine: action.payload.backend || trimmedState.engine,
        configuration_method: action.payload.configuration_method,
        parameters: action.payload.parameters || trimmedState.parameters,
        ssh_tunnel: action.payload.ssh_tunnel || trimmedState.ssh_tunnel,
        query_input,
      };

    case ActionType.DbSelected:
      // set initial state for blank form
      return {
        ...action.payload,
        extra: DEFAULT_EXTRA,
        expose_in_sqllab: true,
      };
    case ActionType.ConfigMethodChange:
      return {
        ...action.payload,
      };

    case ActionType.Reset:
    default:
      return null;
  }
}