ronantakizawa/codereview-bench huggingface dataset:
Repository: hasura/graphql-engine
File: console/src/components/Services/Data/TableBrowseRows/localStorageUtils.ts (TypeScript)

Code before:
    console.error(e);
    setLSState(key, defaultValue);
    return defaultValue;
  }
};

const columnsCollapsedKey = 'data:collapsed';

type CollapesEntry = Record<string, boolean>;
type CollapesState = Record<string, CollapesEntry>;

export const handleCollapseChange = (
  tableName: string,
  schemaName: string,
  collapsedData: CollapesEntry
) => {
  const currentCollapsed = getLSState(columnsCollapsedKey);
  const newCollapsed = {
    ...currentCollapsed,
    [`${schemaName}.${tableName}`]: collapsedData,
  };

  setLSState(columnsCollapsedKey, newCollapsed);
};

export const getCollapsedColumns = (tableName: string, schemaName: string) => {
  const collapsedData = getLSState(columnsCollapsedKey) as CollapesState;
  return collapsedData[`${schemaName}.${tableName}`];
};

const columnsOrderKey = 'data:order';

type OrderEntry = { newOrder: number; defaultOrder: number };
type OrderState = Record<string, OrderEntry[]>;

const compareReorderItems = (item1: OrderEntry) => (item2: OrderEntry) =>
  item1.newOrder === item2.newOrder &&
  item1.defaultOrder === item2.defaultOrder;

export const handleOrderChange = (
  tableName: string,
  schemaName: string,
  orderData: OrderEntry[]
) => {
  const currentOrders = getLSState(columnsOrderKey);

  // remove duplicates
  const newOrders: OrderEntry[] = [];
  orderData.forEach(item => {
    const sameElements = orderData.filter(compareReorderItems(item));
    const alreadyAdded = newOrders.some(compareReorderItems(item));

Review comment (suggestion):
```suggestion
export const getPersistedCollapsedColumns = (tableName: string, schemaName: string) => {
```

Code after:
    }
    return null;
  } catch {
    return null;
  }
};

const columnsCollapsedKey = 'data:collapsed';

type CollapesEntry = Record<string, boolean>;

export const persistColumnCollapseChange = (
  tableName: string,
  schemaName: string,
  collapsedData: CollapesEntry
) => {
  const state = getLSState(columnsCollapsedKey);
  const currentCollapsed = parseLSState(state) || {};
  const newCollapsed = {
    ...currentCollapsed,
    [`${schemaName}.${tableName}`]: collapsedData,
  };

  setLSState(columnsCollapsedKey, JSON.stringify(newCollapsed));
};

export const getPersistedCollapsedColumns = (
  tableName: string,
  schemaName: string
) => {
  const collapsedData = parseLSState(getLSState(columnsCollapsedKey)) || {};
  return collapsedData[`${schemaName}.${tableName}`];
};

const columnsOrderKey = 'data:order';

type OrderEntry = { newOrder: number; defaultOrder: number };

const compareReorderItems = (item1: OrderEntry) => (item2: OrderEntry) =>
  item1.newOrder === item2.newOrder &&
  item1.defaultOrder === item2.defaultOrder;

export const persistColumnOrderChange = (
  tableName: string,
  schemaName: string,
  orderData: OrderEntry[]
) => {
  const currentOrders = parseLSState(getLSState(columnsOrderKey)) || {};

  // remove duplicates
  const newOrders: OrderEntry[] = [];
Repository: hasura/graphql-engine
File: console/src/components/Services/Data/TableBrowseRows/localStorageUtils.ts (TypeScript)

Code before:

    if (sameElements.length % 2 && !alreadyAdded) {
      newOrders.push(item);
    }
  });

  if (!newOrders.length) {
    delete currentOrders[`${schemaName}.${tableName}`];
    setLSState(columnsOrderKey, currentOrders);
    return;
  }

  setLSState(columnsOrderKey, {
    ...currentOrders,
    [`${schemaName}.${tableName}`]: newOrders,
  });
};

export const getColumnsOrder = (tableName: string, schemaName: string) => {
  const orderData = getLSState(columnsOrderKey) as OrderState;
  return orderData[`${schemaName}.${tableName}`];
};

const pageSizeKey = 'data:pageSize';

export const handlePageSizeStateChange = (pageSize: number) => {
  setLSState(pageSizeKey, pageSize);
};

export const getPageSize = () => {
  return getLSState(pageSizeKey, null);
};


Review comment (suggestion):
```suggestion
export const persistPageSizeChange = (pageSize: number) => {
```

Code after:
  orderData.forEach(item => {
    const sameElements = orderData.filter(compareReorderItems(item));
    const alreadyAdded = newOrders.some(compareReorderItems(item));

    if (sameElements.length % 2 && !alreadyAdded) {
      newOrders.push(item);
    }
  });

  if (!newOrders.length) {
    delete currentOrders[`${schemaName}.${tableName}`];
    setLSState(columnsOrderKey, JSON.stringify(currentOrders));
    return;
  }

  setLSState(
    columnsOrderKey,
    JSON.stringify({
      ...currentOrders,
      [`${schemaName}.${tableName}`]: newOrders,
    })
  );
};

export const getPersistedColumnsOrder = (
  tableName: string,
  schemaName: string
) => {
  const orderData = parseLSState(getLSState(columnsOrderKey));
  return orderData ? orderData[`${schemaName}.${tableName}`] : [];
};

const pageSizeKey = 'data:pageSize';

export const persistPageSizeChange = (pageSize: number) => {
  setLSState(pageSizeKey, JSON.stringify(pageSize));
};

export const getPersistedPageSize = () => {
  return parseLSState(getLSState(pageSizeKey));
};
Repository: hasura/graphql-engine
File: console/src/components/UIKit/theme/index.ts (TypeScript)

Code before:
// Theme specification for the Design-System.
type ColorName =
  | 'red'
  | 'green'
  | 'blue'
  | 'orange'
  | 'yellow'
  | 'grey'
  | 'black'
  | 'white'
  | 'transparent'
  | 'tab';

type ColorType =
  | 'original'
  | 'primary'
  | 'secondary'
  | 'hover'
  | 'light'
  | 'border'
  | 'text'
  | 'tab';

type Color = Partial<Record<ColorType, string>> | any;

const colors: Record<ColorName, Color> = {
  red: {
    original: '#ff0000',
    primary: '#e53935',
    hover: 'rgba(229, 57, 53, 0.4)',
    light: '#f7e9e9',
  },
  green: {
    original: '#008000',
    primary: '#69cb43',
    hover: 'rgba(123, 179, 66, 0.4)',
    light: '#f0f8e7',
  },
  blue: {
    original: '#0000ff',
    primary: '#1f88e5',
    hover: 'rgba(31, 136, 229, 0.4)',
    light: '#f0f8ff',
  },
  orange: {
    original: '#ffa500',
    primary: '#fdb02c',
    hover: 'rgba(253, 176, 44, 0.4)',
    light: '#fff8ed',

Review comment (suggestion):
I couldn't find a proper way to handle this with `Union Types` or `Partial`.  Because there are color objects as both `record` and `string`.  What do you think ?  @beerose 

Code after:
// Theme specification for the Design-System.

const colors = {
  red: {
    original: '#ff0000',
    primary: '#e53935',
    hover: 'rgba(229, 57, 53, 0.4)',
    light: '#f7e9e9',
  },
  green: {
    original: '#008000',
    primary: '#69cb43',
    hover: 'rgba(123, 179, 66, 0.4)',
    light: '#f0f8e7',
  },
  blue: {
    original: '#0000ff',
    primary: '#1f88e5',
    hover: 'rgba(31, 136, 229, 0.4)',
    light: '#f0f8ff',
  },
  orange: {
    original: '#ffa500',
    primary: '#fdb02c',
    hover: 'rgba(253, 176, 44, 0.4)',
    light: '#fff8ed',
  },
  yellow: {
    original: '#ffff00',
    primary: '#f8d721',
    hover: 'rgba(204, 177, 25, 0.4)',
  },
  grey: {
    original: '#888888',
    tab: '#939390',
    border: '#ededed',
  },
  black: {
    original: '#000',
    secondary: '#484538',
    text: '#292822',
    hover: 'rgba(0, 0, 0, 0.16)',
  },
  white: '#fff',
  transparent: 'transparent',
  tab: '#1fd6e5',
};

// ********************************** //
Repository: hasura/graphql-engine
File: console/src/components/UIKit/theme/index.ts (TypeScript)

Code before:
  10: 32,
  11: 64,
};
space.zero = space[0];
space.xs = space[1];
space.sm = space[3];
space.md = space[7];
space.lg = space[10];
space.xl = space[11];

// ********************************** //

/* line-height aliases
 * body: 1.5
 * explain: 1.3 ~ Explainer Text
 */

type lineHeight = 'body' | 'explain';

const lineHeights: Partial<Record<number | lineHeight, number>> = {
  0: 1.33,
  1: 1.5,
};
lineHeights.body = lineHeights[1];
lineHeights.explain = lineHeights[0];

// ********************************** //

type Size = 'sm' | 'lg';

/* sizes aliases (width & height)
 * sm: 40px
 * lg: 48px
 */

const sizes: Partial<Record<number | Size, number>> = {
  0: 40,
  1: 48,
};

sizes.sm = sizes[0];
sizes.lg = sizes[1];

// ********************************** //

export const theme = {
  colors,
  radii,
  fonts: {
    roboto: 'Roboto',
  },

Review comment (suggestion):
I didn't quite understand the use case of this.  We could have export all those types above individually as inline.

Or, I misunderstood you. Can you elaborate on this a bit? Thanks! @beerose 

Code after:
export const theme = {
  colors,
  radii,
  fonts: {
    roboto: 'Roboto',
  },
  fontWeights,
  fontSizes,
  sizes,
  space,
  lineHeights,
  shadows: [
    0,
    '0 0 3px 0 rgba(0, 0, 0, 0.16)',
    '0 3px 6px 0 rgba(0, 0, 0, 0.16)',
    '0 3px 10px 0 rgba(0, 0, 0, 0.16)',
    '0 7px 24px 0 rgba(0, 0, 0, 0.32)',
  ],
  borders: [0, '1px solid', '2px solid', '3px solid', '4px solid', '5px solid'],
  button,
  alertBox,
  icon,
};

type _Theme = typeof theme;
export interface Theme extends _Theme {}
Repository: hasura/graphql-engine
File: console/src/components/UIKit/theme/index.ts (TypeScript)

Code before:
  2: 6,
  3: 8,
  4: 10,
  5: 12,
  6: 14,
  7: 16,
  8: 18,
  9: 20,
  10: 32,
  11: 64,
};
space.zero = space[0];
space.xs = space[1];
space.sm = space[3];
space.md = space[7];
space.lg = space[10];
space.xl = space[11];

// ********************************** //

/* line-height aliases
 * body: 1.5
 * explain: 1.3 ~ Explainer Text
 */

type lineHeightKey = 'body' | 'explain';

const lineHeights: Partial<Record<number | lineHeightKey, number>> = {
  0: 1.33,
  1: 1.5,
};
lineHeights.body = lineHeights[1];
lineHeights.explain = lineHeights[0];

// ********************************** //

type SizeKey = 'sm' | 'lg';

/* sizes aliases (width & height)
 * sm: 40px
 * lg: 48px
 */

const sizes: Partial<Record<number | SizeKey, number>> = {
  0: 40,
  1: 48,
};

sizes.sm = sizes[0];
sizes.lg = sizes[1];


Review comment (suggestion):
```suggestion
type LineHeightKey = 'body' | 'explain';
```

Code after:
  2: 6,
  3: 8,
  4: 10,
  5: 12,
  6: 14,
  7: 16,
  8: 18,
  9: 20,
  10: 32,
  11: 64,
};
space.zero = space[0];
space.xs = space[1];
space.sm = space[3];
space.md = space[7];
space.lg = space[10];
space.xl = space[11];

// ********************************** //

/* line-height aliases
 * body: 1.5
 * explain: 1.3 ~ Explainer Text
 */

type LineHeightKey = 'body' | 'explain';

const lineHeights: Partial<Record<number | LineHeightKey, number>> = {
  0: 1.33,
  1: 1.5,
};
lineHeights.body = lineHeights[1];
lineHeights.explain = lineHeights[0];

// ********************************** //

type SizeKey = 'sm' | 'lg';

/* sizes aliases (width & height)
 * sm: 40px
 * lg: 48px
 */

const sizes: Partial<Record<number | SizeKey, number>> = {
  0: 40,
  1: 48,
};

sizes.sm = sizes[0];
sizes.lg = sizes[1];
Repository: hasura/graphql-engine
File: console/src/components/UIKit/theme/index.ts (TypeScript)

Code before:
    color: colors.blue.primary,
  },
  warning: {
    color: colors.orange.primary,
  },
  error: {
    color: colors.red.primary,
  },
  // type ~ out of range
  default: {
    color: colors.black.secondary,
  },
};

// Border Radius ********************* //

/* border-radius aliases
 * xs: 2px (extra small)
 * sm: 4px (small)
 * md: 8px (medium)
 * lg: 12px (large)
 * xl: 16px (extra large)
 * circle: 1000px
 */

type BorderRadiusSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'circle';

const radii: Partial<Record<number | BorderRadiusSize, number>> = {
  0: 0,
  1: 2,
  2: 4,
  3: 8,
  4: 12,
  5: 16,
};

radii.xs = radii[1];
radii.sm = radii[2];
radii.md = radii[3];
radii.lg = radii[4];
radii.xl = radii[5];
radii.circle = 1000;

// ********************************** //

/* font-weight aliases
 * normal: 400
 * medium: 500
 * bold: 700
 */


Review comment (suggestion):
```suggestion
type BorderRadiusKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'circle';
```

Code after:
    color: colors.blue.primary,
  },
  warning: {
    color: colors.orange.primary,
  },
  error: {
    color: colors.red.primary,
  },
  // type ~ out of range
  default: {
    color: colors.black.secondary,
  },
};

// Border Radius ********************* //

/* border-radius aliases
 * xs: 2px (extra small)
 * sm: 4px (small)
 * md: 8px (medium)
 * lg: 12px (large)
 * xl: 16px (extra large)
 * circle: 1000px
 */

type BorderRadiusKey = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'circle';

const radii: Partial<Record<number | BorderRadiusKey, number>> = {
  0: 0,
  1: 2,
  2: 4,
  3: 8,
  4: 12,
  5: 16,
};

radii.xs = radii[1];
radii.sm = radii[2];
radii.md = radii[3];
radii.lg = radii[4];
radii.xl = radii[5];
radii.circle = 1000;

// ********************************** //

/* font-weight aliases
 * normal: 400
 * medium: 500
 * bold: 700
 */
Repository: hasura/graphql-engine
File: server/src-lib/Hasura/Server/Utils.hs (Haskell)

Code before:
    case verInt of
      1 -> return VIVersion1
      2 -> return VIVersion2
      i -> fail $ "expected 1 or 2, encountered " ++ show i

englishList :: NonEmpty Text -> Text
englishList = \case
  one :| []    -> one
  one :| [two] -> one <> " and " <> two
  several      ->
    let final :| initials = NE.reverse several
    in T.intercalate ", " (reverse initials) <> ", and " <> final

makeReasonMessage :: [a] -> (a -> Text) -> Text
makeReasonMessage errors showError =
  case errors of
    [singleError] -> "because " <> showError singleError
    _ -> "for the following reasons:\n" <> T.unlines
         (map (("  • " <>) . showError) errors)

executeJSONPath :: JSONPath -> Value -> IResult Value
executeJSONPath jsonPath = iparse (valueParser jsonPath)
  where
    valueParser path value = case path of
      []                      -> pure value
      [pathElement]           -> parseWithPathElement pathElement value
      (pathElement:remaining) -> parseWithPathElement pathElement value >>=
                                 ((<?> pathElement) . valueParser remaining)
      where
        parseWithPathElement = \case
                  Key k   -> withObject "Object" (.: k)
                  Index i -> withArray "Array" $
                             maybe (fail "Array index out of range") pure . (V.!? i)


Review comment (suggestion):
This case match isn't required :smiley: . The next match handles this case too. Feel free to delete this line. 

Code after:
    case verInt of
      1 -> return VIVersion1
      2 -> return VIVersion2
      i -> fail $ "expected 1 or 2, encountered " ++ show i

englishList :: NonEmpty Text -> Text
englishList = \case
  one :| []    -> one
  one :| [two] -> one <> " and " <> two
  several      ->
    let final :| initials = NE.reverse several
    in T.intercalate ", " (reverse initials) <> ", and " <> final

makeReasonMessage :: [a] -> (a -> Text) -> Text
makeReasonMessage errors showError =
  case errors of
    [singleError] -> "because " <> showError singleError
    _ -> "for the following reasons:\n" <> T.unlines
         (map (("  • " <>) . showError) errors)

executeJSONPath :: JSONPath -> Value -> IResult Value
executeJSONPath jsonPath = iparse (valueParser jsonPath)
  where
    valueParser path value = case path of
      []                      -> pure value
      (pathElement:remaining) -> parseWithPathElement pathElement value >>=
                                 ((<?> pathElement) . valueParser remaining)
      where
        parseWithPathElement = \case
                  Key k   -> withObject "Object" (.: k)
                  Index i -> withArray "Array" $
                             maybe (fail "Array index out of range") pure . (V.!? i)
Repository: hasura/graphql-engine
File: server/src-lib/Hasura/Server/Config.hs (Haskell)

Code before:
module Hasura.Server.Config
  ( runGetConfig
  )
  where

import           Data.Aeson.Casing
import           Data.Aeson.TH

import           Hasura.Prelude
import           Hasura.Server.Auth
import           Hasura.Server.Auth.JWT
import           Hasura.Server.Version                    (HasVersion, Version, currentVersion)
import           Hasura.RQL.Types.Error                   (encodeJSONPath)
import           Data.Text                                (pack)

import qualified Hasura.GraphQL.Execute.LiveQuery.Options as LQ

data JWTInfo
  = JWTInfo
  { jwtiClaimsNamespace     :: !(Maybe Text)
  , jwtiClaimsNamespacePath :: !(Maybe Text)
  , jwtiClaimsFormat        :: !JWTClaimsFormat
  } deriving (Show, Eq)

$(deriveToJSON (aesonDrop 4 snakeCase) ''JWTInfo)

data ServerConfig
  = ServerConfig
  { scfgVersion            :: !Version
  , scfgIsAdminSecretSet   :: !Bool
  , scfgIsAuthHookSet      :: !Bool
  , scfgIsJwtSet           :: !Bool
  , scfgJwt                :: !(Maybe JWTInfo)
  , scfgIsAllowListEnabled :: !Bool
  , scfgLiveQueries        :: !LQ.LiveQueriesOptions
  } deriving (Show, Eq)

$(deriveToJSON (aesonDrop 4 snakeCase) ''ServerConfig)

runGetConfig :: HasVersion => AuthMode -> Bool -> LQ.LiveQueriesOptions -> ServerConfig
runGetConfig am isAllowListEnabled liveQueryOpts = ServerConfig
    currentVersion
    (isAdminSecretSet am)
    (isAuthHookSet am)
    (isJWTSet am)
    (getJWTInfo am)

Review comment (suggestion):
Both claims namespace and namespace path are orthogonal and only one should present at a time. Right? Then you could model these into a sum type.

Code after:
module Hasura.Server.Config
  ( runGetConfig
  )
  where

import           Data.Aeson.Casing
import           Data.Aeson.TH

import           Hasura.Prelude
import           Hasura.Server.Auth
import           Hasura.Server.Auth.JWT
import           Hasura.Server.Version                    (HasVersion, Version, currentVersion)

import qualified Hasura.GraphQL.Execute.LiveQuery.Options as LQ

data JWTInfo
  = JWTInfo
  { jwtiClaimsNamespace     :: !JWTConfigClaims
  , jwtiClaimsFormat        :: !JWTClaimsFormat
  } deriving (Show, Eq)

$(deriveToJSON (aesonDrop 4 snakeCase) ''JWTInfo)

data ServerConfig
  = ServerConfig
  { scfgVersion            :: !Version
  , scfgIsAdminSecretSet   :: !Bool
  , scfgIsAuthHookSet      :: !Bool
  , scfgIsJwtSet           :: !Bool
  , scfgJwt                :: !(Maybe JWTInfo)
  , scfgIsAllowListEnabled :: !Bool
  , scfgLiveQueries        :: !LQ.LiveQueriesOptions
  } deriving (Show, Eq)

$(deriveToJSON (aesonDrop 4 snakeCase) ''ServerConfig)

runGetConfig :: HasVersion => AuthMode -> Bool -> LQ.LiveQueriesOptions -> ServerConfig
runGetConfig am isAllowListEnabled liveQueryOpts = ServerConfig
    currentVersion
    (isAdminSecretSet am)
    (isAuthHookSet am)
    (isJWTSet am)
    (getJWTInfo am)
    isAllowListEnabled
    liveQueryOpts
Repository: hasura/graphql-engine
File: console/src/components/UIKit/atoms/Icon/index.tsx (TypeScript)

Code before:
  FaCog,
  FaQuestion,
} from 'react-icons/fa';

import { theme } from '../../theme';
import { StyledIcon } from './Icon';

const iconReferenceMap = {
  success: FaCheckCircle,
  info: FaInfoCircle,
  warning: FaExclamationTriangle,
  error: FaExclamationCircle,
  graphiql: FaFlask,
  database: FaDatabase,
  schema: FaPlug,
  event: FaCloud,
  settings: FaCog,
  question: FaQuestion,
  default: FaExclamationCircle,
};

const iconWidth = 18;
const iconHeight = 18;

export type IconProps = {
  type: keyof typeof theme.icon;
}

export const Icon: React.FC<IconProps> = props => {
  const { type } = props;

  const { icon } = theme;

  const iconColor = icon[type] ? icon[type].color : icon.default.color;

  const CurrentActiveIcon = iconReferenceMap[type]
    ? iconReferenceMap[type]
    : iconReferenceMap.default;

  return (
    <StyledIcon
      fontSize="icon"
      color={iconColor}
      width={iconWidth}
      height={iconHeight}
      as={CurrentActiveIcon}
      {...props}
    />
  );
};


Review comment (suggestion):
As PR converting theme config to TypeScript was just merged, you may want to use `Theme['icon']`, where `Theme` is an interface exported from `theme.js`.

Code after:
  FaCog,
  FaQuestion,
  FaCogs,
  FaExclamation,
  FaTimes,
  FaStar,
  FaTwitter,
  FaHeart,
  FaChevronRight,
  FaCompressArrowsAlt,
  FaExpand,
  FaEdit,
  FaChevronDown,
  FaSearch,
  FaSpinner,
  FaQuestionCircle,
  FaPause,
  FaPlay,
  FaCheck,
  FaRedoAlt,
  FaChevronUp,
  FaTrashAlt,
  FaPencilAlt,
  FaPlus,
  FaEye,
  FaUserSecret,
  FaRegLightbulb,
  FaSort,
  FaCaretUp,
  FaCaretDown,
  FaCaretRight,
  FaCaretLeft,
  FaRegClone,
  FaRegCaretSquareRight,
  FaCopy,
  FaExternalLinkAlt,
  FaTable,
  FaFilter,
  FaWrench,
  FaRegPaperPlane,
  FaCodeBranch,
  FaGithub,
  FaDownload,
} from 'react-icons/fa';

import { Theme } from '../../theme';
import { StyledIcon } from './Icon';

const iconReferenceMap = {
  success: FaCheckCircle,
  info: FaInfoCircle,
Repository: hasura/graphql-engine
File: console/src/telemetry/Actions.js (JavaScript)

Code before:
          globals.hasuraUUID = data[0].hasura_uuid;
          dispatch({
            type: SET_CONSOLE_OPTS,
            data: data[0].console_state,
          });
          globals.telemetryNotificationShown =
            data[0].console_state.telemetryNotificationShown;
        }
        return Promise.resolve();
      },
      error => {
        console.error(
          'Failed to load console options: ' + JSON.stringify(error)
        );
        return Promise.reject();
      }
    );
  };
};

export const requireConsoleOpts = ({ dispatch }) => (
  nextState,
  replaceState,
  callback
) => {
  dispatch(loadConsoleOpts()).finally(() => {
    callback();
  });
};

const telemetryReducer = (state = defaultTelemetryState, action) => {
  switch (action.type) {
    case SET_CONSOLE_OPTS:
      return {
        ...state,
        console_opts: {
          ...action.data,
        },
      };
    case SET_NOTIFICATION_SHOWN:
      return {
        ...state,
        console_opts: {
          ...state.console_opts,
          telemetryNotificationShown: true,
        },
      };
    case SET_HASURA_UUID:
      return {
        ...state,
        hasura_uuid: action.data,

Review comment (suggestion):
```suggestion
  dispatch(loadConsoleOpts()).finally(callback)
```

Code after:
          globals.hasuraUUID = data[0].hasura_uuid;
          dispatch({
            type: SET_CONSOLE_OPTS,
            data: data[0].console_state,
          });
          globals.telemetryNotificationShown =
            data[0].console_state.telemetryNotificationShown;
        }
        return Promise.resolve();
      },
      error => {
        console.error(
          'Failed to load console options: ' + JSON.stringify(error)
        );
        return Promise.reject();
      }
    );
  };
};

export const requireConsoleOpts = ({ dispatch }) => (
  nextState,
  replaceState,
  callback
) => {
  dispatch(loadConsoleOpts()).finally(callback);
};

const telemetryReducer = (state = defaultTelemetryState, action) => {
  switch (action.type) {
    case SET_CONSOLE_OPTS:
      return {
        ...state,
        console_opts: {
          ...action.data,
        },
      };
    case SET_NOTIFICATION_SHOWN:
      return {
        ...state,
        console_opts: {
          ...state.console_opts,
          telemetryNotificationShown: true,
        },
      };
    case SET_HASURA_UUID:
      return {
        ...state,
        hasura_uuid: action.data,
      };
    default: