<commit_before>import { Action, combineReducers } from 'redux';
import { combineActions, handleActions } from 'redux-actions';
import { get, has, omit, chain, pick, fromPairs } from 'lodash';

import * as Actions from '../actions';
import { IJudge } from '../domain/IJudge';
import {
  GroupWeights,
  ICanaryClassifierThresholdsConfig,
  ICanaryConfig,
  ICanaryJudgeConfig,
  ICanaryMetricConfig
} from '../domain/ICanaryConfig';
import { CanarySettings } from '../canary.settings';
import { IGroupState, group } from './group';
import { JudgeSelectRenderState } from '../edit/judgeSelect';
import { UNGROUPED, ALL } from '../edit/groupTabs';
import { AsyncRequestState } from './asyncRequest';

export interface ILoadState {
  state: AsyncRequestState;
}

export interface ISaveState {
  state: AsyncRequestState;
  error: string;
}

// Mixing destroy/delete here because delete is a JS keyword.
export interface IDestroyState {
  state: AsyncRequestState;
  error: string;
}

export interface IJsonState {
  configJson: string;
  error: string;
}

export interface IJudgeState {
  judgeConfig: ICanaryJudgeConfig;
  renderState: JudgeSelectRenderState;
}

export interface IChangeMetricGroupState {
  toGroup: string;
}

export interface ISelectedConfigState {
  config: ICanaryConfig;
  isInSyncWithServer: boolean;
  metricList: ICanaryMetricConfig[];
  editingMetric: ICanaryMetricConfig;
  thresholds: ICanaryClassifierThresholdsConfig;
  judge: IJudgeState;
  group: IGroupState;
  load: ILoadState;
  save: ISaveState;
  destroy: IDestroyState;
  json: IJsonState;
  changeMetricGroup: IChangeMetricGroupState;
}

const config = handleActions({
  [Actions.SELECT_CONFIG]: (_state: ICanaryConfig, action: Action & any) => action.payload.config,
  [Actions.UPDATE_CONFIG_NAME]: (state: ICanaryConfig, action: Action & any) => ({ ...state, name: action.payload.name }),
  [Actions.UPDATE_CONFIG_DESCRIPTION]: (state: ICanaryConfig, action: Action & any) => ({ ...state, description: action.payload.description }),
  [combineActions(Actions.DELETE_CONFIG_SUCCESS, Actions.CLEAR_SELECTED_CONFIG)]: () => null,
}, null);

const load = combineReducers({
  state: handleActions({
    [Actions.LOAD_CONFIG_REQUEST]: () => AsyncRequestState.Requesting,
    [Actions.LOAD_CONFIG_FAILURE]: () => AsyncRequestState.Failed,
    [Actions.SELECT_CONFIG]: () => AsyncRequestState.Fulfilled,
  }, AsyncRequestState.Requesting),
});

function idMetrics(metrics: ICanaryMetricConfig[] = []) {
  return metrics.map((metric, index) => Object.assign({}, metric, { id: '#' + index }));
}

const metricList = handleActions({
  [Actions.SELECT_CONFIG]: (_state: ICanaryMetricConfig[], action: Action & any) => idMetrics(action.payload.config.metrics),
  [Actions.ADD_METRIC]: (state: ICanaryMetricConfig[], action: Action & any) => idMetrics(state.concat([action.payload.metric])),
  [Actions.REMOVE_METRIC]: (state: ICanaryMetricConfig[], action: Action & any) => idMetrics(state.filter(metric => metric.id !== action.payload.id)),
}, []);

const editingMetric = handleActions({
  [Actions.RENAME_METRIC]: (state: ICanaryMetricConfig, action: Action & any) => ({ ...state, name: action.payload.name }),
  [Actions.UPDATE_STACKDRIVER_METRIC_TYPE]: (state: ICanaryMetricConfig, action: Action & any) => ({
    ...state, query: { ...state.query, metricType: action.metricType, type: 'stackdriver' },
  }),
  [Actions.UPDATE_ATLAS_QUERY]: (state: ICanaryMetricConfig, action: Action & any) => ({
    ...state, query: { ...state.query, q: action.query }
  })
}, null);

const save = combineReducers<ISaveState>({
  state: handleActions({
    [Actions.SAVE_CONFIG_REQUEST]: () => AsyncRequestState.Requesting,
    [combineActions(Actions.SAVE_CONFIG_SUCCESS, Actions.DISMISS_SAVE_CONFIG_ERROR)]: () => AsyncRequestState.Fulfilled,
    [Actions.SAVE_CONFIG_FAILURE]: () => AsyncRequestState.Failed,
  }, AsyncRequestState.Fulfilled),
  error: handleActions({
    [Actions.SAVE_CONFIG_FAILURE]: (_state: string, action: Action & any) => get(action, 'payload.error.data.message', null),
  }, null),
});

const destroy = combineReducers<IDestroyState>({
  state: handleActions({
    [Actions.DELETE_CONFIG_REQUEST]: () => AsyncRequestState.Requesting,
    [Actions.DELETE_CONFIG_SUCCESS]: () => AsyncRequestState.Fulfilled,
    [Actions.DELETE_CONFIG_FAILURE]: () => AsyncRequestState.Failed,
  }, AsyncRequestState.Fulfilled),
  error: handleActions({
    [Actions.DELETE_CONFIG_FAILURE]: (_state: string, action: Action & any) => get(action, 'payload.error.data.message', null),
  }, null),
});

const json = combineReducers<IJsonState>({
  configJson: handleActions({
    [Actions.SET_CONFIG_JSON]: (_state: IJsonState, action: Action & any) => action.payload.json,
    [combineActions(Actions.CONFIG_JSON_MODAL_CLOSE, Actions.SELECT_CONFIG)]: (): void => null,
  }, null),
  error: handleActions({
    [combineActions(Actions.CONFIG_JSON_MODAL_CLOSE, Actions.SELECT_CONFIG, Actions.SET_CONFIG_JSON)]: () => null,
    [Actions.SET_CONFIG_JSON]: (_state: IJsonState, action: Action & any) => {
      try {
        JSON.parse(action.payload.json);
        return null;
      } catch (e) {
        return e.message;
      }
    }
  }, null),
});


const judge = combineReducers<IJudgeState>({
  judgeConfig: handleActions({
    [Actions.SELECT_JUDGE_NAME]: (state: IJudge, action: Action & any) => ({ ...state, name: action.payload.judge.name }),
  }, null),
  renderState: handleActions({}, JudgeSelectRenderState.None),
});

const thresholds = handleActions({
  [Actions.SELECT_CONFIG]: (_state: ICanaryClassifierThresholdsConfig, action: Action & any) => {
    if (has(action, 'payload.config.classifier.scoreThresholds')) {
      return action.payload.config.classifier.scoreThresholds;
    } else {
      return {
        pass: null,
        marginal: null,
      };
    }
  },
  [Actions.UPDATE_SCORE_THRESHOLDS]: (_state: ICanaryClassifierThresholdsConfig, action: Action & any) => ({
    pass: action.payload.pass, marginal: action.payload.marginal
  })
}, null);

const changeMetricGroup = combineReducers<IChangeMetricGroupState>({
  toGroup: handleActions({
    [Actions.CHANGE_METRIC_GROUP_SELECT]: (_state: string, action: Action & any) => action.payload.group,
  }, null),
});

const isInSyncWithServer = handleActions({}, null);

// This reducer needs to be able to access both metricList and editingMetric so it won't fit the combineReducers paradigm.
function editingMetricReducer(state: ISelectedConfigState = null, action: Action & any): ISelectedConfigState {
  switch (action.type) {
    case Actions.EDIT_METRIC_BEGIN:
      return Object.assign({}, state, {
        editingMetric: state.metricList.find(metric => metric.id === action.payload.id)
      });

    case Actions.EDIT_METRIC_CONFIRM:
      return Object.assign({}, state, {
        metricList: state.metricList.map(metric => metric.id === state.editingMetric.id ? state.editingMetric : metric),
        editingMetric: null
      });

    case Actions.EDIT_METRIC_CANCEL:
      return Object.assign({}, state, {
        editingMetric: null
      });
<commit_msg>feat: auto-edit newly added metric (#127)<commit_after>import { Action, combineReducers } from 'redux';
import { combineActions, handleActions } from 'redux-actions';
import { get, has, omit, chain, pick, fromPairs } from 'lodash';

import * as Actions from '../actions';
import { IJudge } from '../domain/IJudge';
import {
  GroupWeights,
  ICanaryClassifierThresholdsConfig,
  ICanaryConfig,
  ICanaryJudgeConfig,
  ICanaryMetricConfig
} from '../domain/ICanaryConfig';
import { CanarySettings } from '../canary.settings';
import { IGroupState, group } from './group';
import { JudgeSelectRenderState } from '../edit/judgeSelect';
import { UNGROUPED, ALL } from '../edit/groupTabs';
import { AsyncRequestState } from './asyncRequest';

export interface ILoadState {
  state: AsyncRequestState;
}

export interface ISaveState {
  state: AsyncRequestState;
  error: string;
}

// Mixing destroy/delete here because delete is a JS keyword.
export interface IDestroyState {
  state: AsyncRequestState;
  error: string;
}

export interface IJsonState {
  configJson: string;
  error: string;
}

export interface IJudgeState {
  judgeConfig: ICanaryJudgeConfig;
  renderState: JudgeSelectRenderState;
}

export interface IChangeMetricGroupState {
  toGroup: string;
}

export interface ISelectedConfigState {
  config: ICanaryConfig;
  isInSyncWithServer: boolean;
  metricList: ICanaryMetricConfig[];
  editingMetric: ICanaryMetricConfig;
  thresholds: ICanaryClassifierThresholdsConfig;
  judge: IJudgeState;
  group: IGroupState;
  load: ILoadState;
  save: ISaveState;
  destroy: IDestroyState;
  json: IJsonState;
  changeMetricGroup: IChangeMetricGroupState;
}

const config = handleActions({
  [Actions.SELECT_CONFIG]: (_state: ICanaryConfig, action: Action & any) => action.payload.config,
  [Actions.UPDATE_CONFIG_NAME]: (state: ICanaryConfig, action: Action & any) => ({ ...state, name: action.payload.name }),
  [Actions.UPDATE_CONFIG_DESCRIPTION]: (state: ICanaryConfig, action: Action & any) => ({ ...state, description: action.payload.description }),
  [combineActions(Actions.DELETE_CONFIG_SUCCESS, Actions.CLEAR_SELECTED_CONFIG)]: () => null,
}, null);

const load = combineReducers({
  state: handleActions({
    [Actions.LOAD_CONFIG_REQUEST]: () => AsyncRequestState.Requesting,
    [Actions.LOAD_CONFIG_FAILURE]: () => AsyncRequestState.Failed,
    [Actions.SELECT_CONFIG]: () => AsyncRequestState.Fulfilled,
  }, AsyncRequestState.Requesting),
});

function idMetrics(metrics: ICanaryMetricConfig[] = []) {
  return metrics.map((metric, index) => Object.assign({}, metric, { id: '#' + index }));
}

const metricList = handleActions({
  [Actions.SELECT_CONFIG]: (_state: ICanaryMetricConfig[], action: Action & any) => idMetrics(action.payload.config.metrics),
  [Actions.ADD_METRIC]: (state: ICanaryMetricConfig[], action: Action & any) => idMetrics(state.concat([action.payload.metric])),
  [Actions.REMOVE_METRIC]: (state: ICanaryMetricConfig[], action: Action & any) => idMetrics(state.filter(metric => metric.id !== action.payload.id)),
}, []);

const editingMetric = handleActions({
  [Actions.RENAME_METRIC]: (state: ICanaryMetricConfig, action: Action & any) => ({ ...state, name: action.payload.name }),
  [Actions.UPDATE_STACKDRIVER_METRIC_TYPE]: (state: ICanaryMetricConfig, action: Action & any) => ({
    ...state, query: { ...state.query, metricType: action.metricType, type: 'stackdriver' },
  }),
  [Actions.UPDATE_ATLAS_QUERY]: (state: ICanaryMetricConfig, action: Action & any) => ({
    ...state, query: { ...state.query, q: action.query }
  })
}, null);

const save = combineReducers<ISaveState>({
  state: handleActions({
    [Actions.SAVE_CONFIG_REQUEST]: () => AsyncRequestState.Requesting,
    [combineActions(Actions.SAVE_CONFIG_SUCCESS, Actions.DISMISS_SAVE_CONFIG_ERROR)]: () => AsyncRequestState.Fulfilled,
    [Actions.SAVE_CONFIG_FAILURE]: () => AsyncRequestState.Failed,
  }, AsyncRequestState.Fulfilled),
  error: handleActions({
    [Actions.SAVE_CONFIG_FAILURE]: (_state: string, action: Action & any) => get(action, 'payload.error.data.message', null),
  }, null),
});

const destroy = combineReducers<IDestroyState>({
  state: handleActions({
    [Actions.DELETE_CONFIG_REQUEST]: () => AsyncRequestState.Requesting,
    [Actions.DELETE_CONFIG_SUCCESS]: () => AsyncRequestState.Fulfilled,
    [Actions.DELETE_CONFIG_FAILURE]: () => AsyncRequestState.Failed,
  }, AsyncRequestState.Fulfilled),
  error: handleActions({
    [Actions.DELETE_CONFIG_FAILURE]: (_state: string, action: Action & any) => get(action, 'payload.error.data.message', null),
  }, null),
});

const json = combineReducers<IJsonState>({
  configJson: handleActions({
    [Actions.SET_CONFIG_JSON]: (_state: IJsonState, action: Action & any) => action.payload.json,
    [combineActions(Actions.CONFIG_JSON_MODAL_CLOSE, Actions.SELECT_CONFIG)]: (): void => null,
  }, null),
  error: handleActions({
    [combineActions(Actions.CONFIG_JSON_MODAL_CLOSE, Actions.SELECT_CONFIG, Actions.SET_CONFIG_JSON)]: () => null,
    [Actions.SET_CONFIG_JSON]: (_state: IJsonState, action: Action & any) => {
      try {
        JSON.parse(action.payload.json);
        return null;
      } catch (e) {
        return e.message;
      }
    }
  }, null),
});


const judge = combineReducers<IJudgeState>({
  judgeConfig: handleActions({
    [Actions.SELECT_JUDGE_NAME]: (state: IJudge, action: Action & any) => ({ ...state, name: action.payload.judge.name }),
  }, null),
  renderState: handleActions({}, JudgeSelectRenderState.None),
});

const thresholds = handleActions({
  [Actions.SELECT_CONFIG]: (_state: ICanaryClassifierThresholdsConfig, action: Action & any) => {
    if (has(action, 'payload.config.classifier.scoreThresholds')) {
      return action.payload.config.classifier.scoreThresholds;
    } else {
      return {
        pass: null,
        marginal: null,
      };
    }
  },
  [Actions.UPDATE_SCORE_THRESHOLDS]: (_state: ICanaryClassifierThresholdsConfig, action: Action & any) => ({
    pass: action.payload.pass, marginal: action.payload.marginal
  })
}, null);

const changeMetricGroup = combineReducers<IChangeMetricGroupState>({
  toGroup: handleActions({
    [Actions.CHANGE_METRIC_GROUP_SELECT]: (_state: string, action: Action & any) => action.payload.group,
  }, null),
});

const isInSyncWithServer = handleActions({}, null);

// This reducer needs to be able to access both metricList and editingMetric so it won't fit the combineReducers paradigm.
function editingMetricReducer(state: ISelectedConfigState = null, action: Action & any): ISelectedConfigState {
  switch (action.type) {
    case Actions.ADD_METRIC:
      return Object.assign({}, state, {
        editingMetric: state.metricList[state.metricList.length - 1]
      });

    case Actions.EDIT_METRIC_BEGIN:
      return Object.assign({}, state, {
        editingMetric: state.metricList.find(metric => metric.id === action.payload.id)
      });

    case Actions.EDIT_METRIC_CONFIRM:
      return Object.assign({}, state, {
        metricList: state.metricList.map(metric => metric.id === state.editingMetric.id ? state.editingMetric : metric),
        editingMetric: null
      });

    case Actions.EDIT_METRIC_CANCEL:
      return Object.assign({}, state, {
        editingMetric: null
      });

{'fraction_non_alphanumeric': 0.13900256641464936, 'fraction_numerical': 0.0002774502323645696, 'mean_word_length': 4.769507803121249, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 79, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1198914', 'n_tokens_mistral': 4463, 'n_tokens_neox': 4414, 'n_words': 933}