<commit_before>import i from "icepick";
import _ from "underscore";
import moment from "moment";

import { createAction } from "redux-actions";
import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import { normalize, Schema, arrayOf } from "normalizr";

import MetabaseAnalytics from "metabase/lib/analytics";
import { getPositionForNewDashCard } from "metabase/lib/dashboard_grid";

import { fetchDatabaseMetadata } from "./metadata";

const DATASET_SLOW_TIMEOUT   = 15 * 1000;
const DATASET_USUALLY_FAST_THRESHOLD = 15 * 1000;

// normalizr schemas
const dashcard = new Schema('dashcard');
const card = new Schema('card');
const dashboard = new Schema('dashboard');
dashboard.define({
    ordered_cards: arrayOf(dashcard)
});

// action constants
export const SET_EDITING_DASHBOARD = 'SET_EDITING';

export const FETCH_CARDS = 'FETCH_CARDS';
export const DELETE_CARD = 'DELETE_CARD';

export const FETCH_DASHBOARD = 'FETCH_DASHBOARD';
export const SET_DASHBOARD_ATTRIBUTES = 'SET_DASHBOARD_ATTRIBUTES';
export const SET_DASHCARD_VISUALIZATION_SETTING = 'SET_DASHCARD_VISUALIZATION_SETTING';
export const SAVE_DASHBOARD = 'SAVE_DASHBOARD';
export const DELETE_DASHBOARD = 'DELETE_DASHBOARD';

export const ADD_CARD_TO_DASH = 'ADD_CARD_TO_DASH';
export const REMOVE_CARD_FROM_DASH = 'REMOVE_CARD_FROM_DASH';
export const SET_DASHCARD_ATTRIBUTES = 'SET_DASHCARD_ATTRIBUTES';
export const SAVE_DASHCARD = 'SAVE_DASHCARD';

export const FETCH_CARD_DATA = 'FETCH_CARD_DATA';
export const FETCH_CARD_DURATION = 'FETCH_CARD_DURATION';
export const FETCH_REVISIONS = 'FETCH_REVISIONS';
export const REVERT_TO_REVISION = 'REVERT_TO_REVISION';

export const MARK_NEW_CARD_SEEN = 'MARK_NEW_CARD_SEEN';

export const FETCH_DATABASE_METADATA = 'FETCH_DATABASE_METADATA';

export const SET_EDITING_PARAMETER_ID = 'SET_EDITING_PARAMETER_ID';
export const ADD_PARAMETER = 'ADD_PARAMETER';
export const SET_PARAMETER_MAPPING = 'SET_PARAMETER_MAPPING';
export const SET_PARAMETER_NAME = 'SET_PARAMETER_NAME';
export const SET_PARAMETER_VALUE = 'SET_PARAMETER_VALUE';
export const SET_PARAMETER_DEFAULT_VALUE = 'SET_PARAMETER_DEFAULT_VALUE';

// resource wrappers
const DashboardApi = new AngularResourceProxy("Dashboard", ["get", "update", "delete", "reposition_cards", "addcard", "removecard"]);
const MetabaseApi = new AngularResourceProxy("Metabase", ["dataset", "dataset_duration", "db_metadata"]);
const CardApi = new AngularResourceProxy("Card", ["list", "update", "delete"]);
const RevisionApi = new AngularResourceProxy("Revision", ["list", "revert"]);

// action creators

export const setEditingDashboard = createAction(SET_EDITING_DASHBOARD);

export const markNewCardSeen = createAction(MARK_NEW_CARD_SEEN);

// these operations don't get saved to server immediately
export const setDashboardAttributes = createAction(SET_DASHBOARD_ATTRIBUTES);
export const setDashCardAttributes = createAction(SET_DASHCARD_ATTRIBUTES);

export const fetchCards = createThunkAction(FETCH_CARDS, function(filterMode = "all") {
    return async function(dispatch, getState) {
        let cards = await CardApi.list({ f: filterMode });
        for (var c of cards) {
            c.updated_at = moment(c.updated_at);
        }
        return normalize(cards, arrayOf(card));
    };
});

export const deleteCard = createThunkAction(DELETE_CARD, function(cardId) {
    return async function(dispatch, getState) {
        await CardApi.delete({ cardId });
        return cardId;
    };
});

export const addCardToDashboard = function({ dashId, cardId }) {
    return function(dispatch, getState) {
        const { dashboards, dashcards, cards } = getState().dashboard;
        const existingCards = dashboards[dashId].ordered_cards.map(id => dashcards[id]).filter(dc => !dc.isRemoved);
        const id = Math.random(); // temporary id
        dispatch(createAction(ADD_CARD_TO_DASH)({
            id: id,
            dashboard_id: dashId,
            card_id: cardId,
            card: cards[cardId],
            ...getPositionForNewDashCard(existingCards)
        }));
    };
}

export const removeCardFromDashboard = createAction(REMOVE_CARD_FROM_DASH);

export const fetchCardData = createThunkAction(FETCH_CARD_DATA, function(card, dashcard) {
    return async function(dispatch, getState) {
        let result = null;
        let slowCardTimer = setTimeout(() => {
            if (result === null) {
                dispatch(fetchCardDuration(card));
            }
        }, DATASET_SLOW_TIMEOUT);

        // if we have a parameter, apply it to the card query before we execute
        let { dashboardId } = getState().router.params;
        let { dashboards, parameterValues } = getState().dashboard;

        let dashboard = dashboards[dashboardId];

        let parameters = [];
        if (dashboard && dashboard.parameters) {
            for (const parameter of dashboard.parameters) {
                let mapping = _.findWhere(dashcard && dashcard.parameter_mappings, { card_id: card.id, parameter_id: parameter.id });
                if (parameterValues[parameter.id] != null) {
                    parameters.push({
                        type: parameter.type,
                        target: mapping && mapping.target,
                        value: parameterValues[parameter.id]
                    });
                }
            }
        }

        result = await MetabaseApi.dataset({
            ...card.dataset_query,
            parameters
        });

        clearTimeout(slowCardTimer);
        return { dashcard_id: dashcard.id, card_id: card.id, result };
    };
});

export const fetchCardDuration = createThunkAction(FETCH_CARD_DURATION, function(card) {
    return async function(dispatch, getState) {
        let result = await MetabaseApi.dataset_duration(card.dataset_query);
        return {
            id: card.id,
            result: {
                fast_threshold: DATASET_USUALLY_FAST_THRESHOLD,
                ...result
            }
        };
    };
});

export const fetchDashboard = createThunkAction(FETCH_DASHBOARD, function(id, enableQueryParameters = true, enableDefaultParameters = true) {
    return async function(dispatch, getState) {
        let result = await DashboardApi.get({ dashId: id });
        if (result.parameters) {
            const { query } = getState().router.location;
            for (const parameter of result.parameters) {
                if (enableQueryParameters && query[parameter.slug] != null) {
                    dispatch(setParameterValue(parameter.id, query[parameter.slug]));
                } else if (enableDefaultParameters && parameter.default != null) {
                    dispatch(setParameterValue(parameter.id, parameter.default));
                }
            }
        }
        _.chain(result.ordered_cards)
            .map((dc) => [dc.card].concat(dc.series))
            .flatten()
            .pluck("database_id")
            .uniq()
            .each((dbId) => dispatch(fetchDatabaseMetadata(dbId)));

        return normalize(result, dashboard);
    };
});

export const saveDashboard = createThunkAction(SAVE_DASHBOARD, function(dashId) {
    return async function(dispatch, getState) {
        let { dashboards, dashcards } = getState().dashboard;
        let dashboard = {
            ...dashboards[dashId],
            ordered_cards: dashboards[dashId].ordered_cards.map(dashcardId => dashcards[dashcardId])
        };

        // remove isRemoved dashboards
        await Promise.all(dashboard.ordered_cards
            .filter(dc => dc.isRemoved && !dc.isAdded)
            .map(dc => DashboardApi.removecard({ dashId: dashboard.id, dashcardId: dc.id })));

        // add isAdded dashboards
        let updatedDashcards = await Promise.all(dashboard.ordered_cards
            .filter(dc => !dc.isRemoved)
            .map(async dc => {
                if (dc.isAdded) {
                    let result = await DashboardApi.addcard({ dashId, cardId: dc.card_id })
                    // mark isAdded because addcard doesn't record the position
                    return { ...result, col: dc.col, row: dc.row, sizeX: dc.sizeX, sizeY: dc.sizeY, series: dc.series, parameter_mappings: dc.parameter_mappings, isAdded: true }
                } else {
                    return dc;
                }
            }));

        // update modified cards
        await Promise.all(dashboard.ordered_cards
            .filter(dc => dc.card.isDirty)
            .map(async dc => CardApi.update(dc.card)));

        // update the dashboard itself
        if (dashboard.isDirty) {
            let { id, name, description, public_perms, parameters } = dashboard;
            dashboard = await DashboardApi.update({ id, name, description, public_perms, parameters });
        }

        // reposition the cards
        if (_.some(updatedDashcards, (dc) => dc.isDirty || dc.isAdded)) {
            let cards = updatedDashcards.map(({ id, row, col, sizeX, sizeY, series, parameter_mappings }) => ({ id, row, col, sizeX, sizeY, series, parameter_mappings }));
            var result = await DashboardApi.reposition_cards({ dashId, cards });
            if (result.status !== "ok") {
                throw new Error(result.status);
            }
        }

        // make sure that we've fully cleared out any dirty state from editing (this is overkill, but simple)
        dispatch(fetchDashboard(dashId, false, true)); // disable using query parameters when saving

        MetabaseAnalytics.trackEvent("Dashboard", "Update");

        return dashboard;
    };
});

export const deleteDashboard = createThunkAction(DELETE_DASHBOARD, function(dashId) {
    return async function(dispatch, getState) {
        await DashboardApi.delete({ dashId });
        MetabaseAnalytics.trackEvent("Dashboard", "Delete");
        return dashId;
    };
});

export const fetchRevisions = createThunkAction(FETCH_REVISIONS, function({ entity, id }) {
    return async function(dispatch, getState) {
        let revisions = await RevisionApi.list({ entity, id });
        return { entity, id, revisions };
    };
});

export const revertToRevision = createThunkAction(REVERT_TO_REVISION, function({ entity, id, revision_id }) {
    return async function(dispatch, getState) {
        await RevisionApi.revert({ entity, id, revision_id });
    };
});

export const setDashCardVisualizationSetting = createAction(SET_DASHCARD_VISUALIZATION_SETTING);

export const setEditingParameterId = createAction(SET_EDITING_PARAMETER_ID);
export const setParameterMapping = createThunkAction(SET_PARAMETER_MAPPING, (parameter_id, dashcard_id, card_id, target) =>
    (dispatch, getState) => {
        let parameter_mappings = getState().dashboard.dashcards[dashcard_id].parameter_mappings || [];
        parameter_mappings = parameter_mappings.filter(m => m.card_id !== card_id || m.parameter_id !== parameter_id);
        if (target) {
            parameter_mappings = parameter_mappings.concat({ parameter_id, card_id, target })
        }
        dispatch(setDashCardAttributes({ id: dashcard_id, attributes: { parameter_mappings }}));
    }
);

export const setParameterValue = createThunkAction(SET_PARAMETER_VALUE, (parameterId, value) =>
    (dispatch, getState) => {
        return { id: parameterId, value };
    }
)

// reducers

const isEditing = handleActions({
    [SET_EDITING_DASHBOARD]: { next: (state, { payload }) => payload }
}, false);

const cards = handleActions({
    [FETCH_CARDS]: { next: (state, { payload }) => ({ ...payload.entities.card }) }
}, {});

const cardList = handleActions({
    [FETCH_CARDS]: { next: (state, { payload }) => payload.result },
    [DELETE_CARD]: { next: (state, { payload }) => state }
}, null);

const dashboards = handleActions({
    [FETCH_DASHBOARD]: { next: (state, { payload }) => ({ ...state, ...payload.entities.dashboard }) },
    [SET_DASHBOARD_ATTRIBUTES]: {
        next: (state, { payload: { id, attributes } }) => ({
            ...state,
            [id]: { ...state[id], ...attributes, isDirty: true }
        })
    },
    [ADD_CARD_TO_DASH]: (state, { payload: dashcard }) => ({
        ...state, [dashcard.dashboard_id]: { ...state[dashcard.dashboard_id], ordered_cards: [...state[dashcard.dashboard_id].ordered_cards, dashcard.id] }
    }),
}, {});

const dashcards = handleActions({
    [FETCH_DASHBOARD]:  { next: (state, { payload }) => ({ ...state, ...payload.entities.dashcard }) },
    [SET_DASHCARD_ATTRIBUTES]: {
        next: (state, { payload: { id, attributes } }) => ({
            ...state,
            [id]: { ...state[id], ...attributes, isDirty: true }
        })
    },
    [SET_DASHCARD_VISUALIZATION_SETTING]: {
        next: (state, { payload: { id, setting, value } }) =>
            i.chain(state)
                .assocIn([id, "card", "visualization_settings"].concat(setting), value)
                .assocIn([id, "card", "isDirty"], true)
                .value()
    },
    [ADD_CARD_TO_DASH]: (state, { payload: dashcard }) => ({
        ...state,
        [dashcard.id]: { ...dashcard, isAdded: true, justAdded: true }
    }),
    [REMOVE_CARD_FROM_DASH]: (state, { payload: { dashcardId }}) => ({
        ...state,
        [dashcardId]: { ...state[dashcardId], isRemoved: true }
    }),
    [MARK_NEW_CARD_SEEN]: (state, { payload: dashcardId }) => ({
        ...state,
        [dashcardId]: { ...state[dashcardId], justAdded: false }
    })
}, {});

const editingParameterId = handleActions({
    [SET_EDITING_PARAMETER_ID]: { next: (state, { payload }) => payload }
}, null);

const revisions = handleActions({
    [FETCH_REVISIONS]: { next: (state, { payload: { entity, id, revisions } }) => ({ ...state, [entity+'-'+id]: revisions })}
}, {});

const dashcardData = handleActions({
    [FETCH_CARD_DATA]: { next: (state, { payload: { dashcard_id, card_id, result }}) => i.assocIn(state, [dashcard_id, card_id], result)}
}, {});

const cardDurations = handleActions({
    [FETCH_CARD_DURATION]: { next: (state, { payload: { id, result }}) => ({ ...state, [id]: result }) }
}, {});

const parameterValues = handleActions({
    [SET_PARAMETER_VALUE]: { next: (state, { payload: { id, value }}) => i.assoc(state, id, value) }
}, {});

export default combineReducers({
    isEditing,
    cards,
    cardList,
    dashboards,
    dashcards,
    editingParameterId,
    revisions,
    dashcardData,
    cardDurations,
    parameterValues
});
<commit_msg>Fix dashboard database metadata loading<commit_after>import i from "icepick";
import _ from "underscore";
import moment from "moment";

import { createAction } from "redux-actions";
import { handleActions, combineReducers, AngularResourceProxy, createThunkAction } from "metabase/lib/redux";
import { normalize, Schema, arrayOf } from "normalizr";

import MetabaseAnalytics from "metabase/lib/analytics";
import { getPositionForNewDashCard } from "metabase/lib/dashboard_grid";

import { fetchDatabaseMetadata } from "./metadata";

const DATASET_SLOW_TIMEOUT   = 15 * 1000;
const DATASET_USUALLY_FAST_THRESHOLD = 15 * 1000;

// normalizr schemas
const dashcard = new Schema('dashcard');
const card = new Schema('card');
const dashboard = new Schema('dashboard');
dashboard.define({
    ordered_cards: arrayOf(dashcard)
});

// action constants
export const SET_EDITING_DASHBOARD = 'SET_EDITING';

export const FETCH_CARDS = 'FETCH_CARDS';
export const DELETE_CARD = 'DELETE_CARD';

export const FETCH_DASHBOARD = 'FETCH_DASHBOARD';
export const SET_DASHBOARD_ATTRIBUTES = 'SET_DASHBOARD_ATTRIBUTES';
export const SET_DASHCARD_VISUALIZATION_SETTING = 'SET_DASHCARD_VISUALIZATION_SETTING';
export const SAVE_DASHBOARD = 'SAVE_DASHBOARD';
export const DELETE_DASHBOARD = 'DELETE_DASHBOARD';

export const ADD_CARD_TO_DASH = 'ADD_CARD_TO_DASH';
export const REMOVE_CARD_FROM_DASH = 'REMOVE_CARD_FROM_DASH';
export const SET_DASHCARD_ATTRIBUTES = 'SET_DASHCARD_ATTRIBUTES';
export const SAVE_DASHCARD = 'SAVE_DASHCARD';

export const FETCH_CARD_DATA = 'FETCH_CARD_DATA';
export const FETCH_CARD_DURATION = 'FETCH_CARD_DURATION';
export const FETCH_REVISIONS = 'FETCH_REVISIONS';
export const REVERT_TO_REVISION = 'REVERT_TO_REVISION';

export const MARK_NEW_CARD_SEEN = 'MARK_NEW_CARD_SEEN';

export const FETCH_DATABASE_METADATA = 'FETCH_DATABASE_METADATA';

export const SET_EDITING_PARAMETER_ID = 'SET_EDITING_PARAMETER_ID';
export const ADD_PARAMETER = 'ADD_PARAMETER';
export const SET_PARAMETER_MAPPING = 'SET_PARAMETER_MAPPING';
export const SET_PARAMETER_NAME = 'SET_PARAMETER_NAME';
export const SET_PARAMETER_VALUE = 'SET_PARAMETER_VALUE';
export const SET_PARAMETER_DEFAULT_VALUE = 'SET_PARAMETER_DEFAULT_VALUE';

// resource wrappers
const DashboardApi = new AngularResourceProxy("Dashboard", ["get", "update", "delete", "reposition_cards", "addcard", "removecard"]);
const MetabaseApi = new AngularResourceProxy("Metabase", ["dataset", "dataset_duration", "db_metadata"]);
const CardApi = new AngularResourceProxy("Card", ["list", "update", "delete"]);
const RevisionApi = new AngularResourceProxy("Revision", ["list", "revert"]);

// action creators

export const setEditingDashboard = createAction(SET_EDITING_DASHBOARD);

export const markNewCardSeen = createAction(MARK_NEW_CARD_SEEN);

// these operations don't get saved to server immediately
export const setDashboardAttributes = createAction(SET_DASHBOARD_ATTRIBUTES);
export const setDashCardAttributes = createAction(SET_DASHCARD_ATTRIBUTES);

export const fetchCards = createThunkAction(FETCH_CARDS, function(filterMode = "all") {
    return async function(dispatch, getState) {
        let cards = await CardApi.list({ f: filterMode });
        for (var c of cards) {
            c.updated_at = moment(c.updated_at);
        }
        return normalize(cards, arrayOf(card));
    };
});

export const deleteCard = createThunkAction(DELETE_CARD, function(cardId) {
    return async function(dispatch, getState) {
        await CardApi.delete({ cardId });
        return cardId;
    };
});

export const addCardToDashboard = function({ dashId, cardId }) {
    return function(dispatch, getState) {
        const { dashboards, dashcards, cards } = getState().dashboard;
        const existingCards = dashboards[dashId].ordered_cards.map(id => dashcards[id]).filter(dc => !dc.isRemoved);
        const id = Math.random(); // temporary id
        dispatch(createAction(ADD_CARD_TO_DASH)({
            id: id,
            dashboard_id: dashId,
            card_id: cardId,
            card: cards[cardId],
            ...getPositionForNewDashCard(existingCards)
        }));
    };
}

export const removeCardFromDashboard = createAction(REMOVE_CARD_FROM_DASH);

export const fetchCardData = createThunkAction(FETCH_CARD_DATA, function(card, dashcard) {
    return async function(dispatch, getState) {
        let result = null;
        let slowCardTimer = setTimeout(() => {
            if (result === null) {
                dispatch(fetchCardDuration(card));
            }
        }, DATASET_SLOW_TIMEOUT);

        // if we have a parameter, apply it to the card query before we execute
        let { dashboardId } = getState().router.params;
        let { dashboards, parameterValues } = getState().dashboard;

        let dashboard = dashboards[dashboardId];

        let parameters = [];
        if (dashboard && dashboard.parameters) {
            for (const parameter of dashboard.parameters) {
                let mapping = _.findWhere(dashcard && dashcard.parameter_mappings, { card_id: card.id, parameter_id: parameter.id });
                if (parameterValues[parameter.id] != null) {
                    parameters.push({
                        type: parameter.type,
                        target: mapping && mapping.target,
                        value: parameterValues[parameter.id]
                    });
                }
            }
        }

        result = await MetabaseApi.dataset({
            ...card.dataset_query,
            parameters
        });

        clearTimeout(slowCardTimer);
        return { dashcard_id: dashcard.id, card_id: card.id, result };
    };
});

export const fetchCardDuration = createThunkAction(FETCH_CARD_DURATION, function(card) {
    return async function(dispatch, getState) {
        let result = await MetabaseApi.dataset_duration(card.dataset_query);
        return {
            id: card.id,
            result: {
                fast_threshold: DATASET_USUALLY_FAST_THRESHOLD,
                ...result
            }
        };
    };
});

export const fetchDashboard = createThunkAction(FETCH_DASHBOARD, function(id, enableQueryParameters = true, enableDefaultParameters = true) {
    return async function(dispatch, getState) {
        let result = await DashboardApi.get({ dashId: id });
        if (result.parameters) {
            const { query } = getState().router.location;
            for (const parameter of result.parameters) {
                if (enableQueryParameters && query[parameter.slug] != null) {
                    dispatch(setParameterValue(parameter.id, query[parameter.slug]));
                } else if (enableDefaultParameters && parameter.default != null) {
                    dispatch(setParameterValue(parameter.id, parameter.default));
                }
            }
        }
        _.chain(result.ordered_cards)
            .map((dc) => [dc.card].concat(dc.series))
            .flatten()
            .map(card => card.dataset_query && card.dataset_query.database)
            .uniq()
            .each((dbId) => dispatch(fetchDatabaseMetadata(dbId)));

        return normalize(result, dashboard);
    };
});

export const saveDashboard = createThunkAction(SAVE_DASHBOARD, function(dashId) {
    return async function(dispatch, getState) {
        let { dashboards, dashcards } = getState().dashboard;
        let dashboard = {
            ...dashboards[dashId],
            ordered_cards: dashboards[dashId].ordered_cards.map(dashcardId => dashcards[dashcardId])
        };

        // remove isRemoved dashboards
        await Promise.all(dashboard.ordered_cards
            .filter(dc => dc.isRemoved && !dc.isAdded)
            .map(dc => DashboardApi.removecard({ dashId: dashboard.id, dashcardId: dc.id })));

        // add isAdded dashboards
        let updatedDashcards = await Promise.all(dashboard.ordered_cards
            .filter(dc => !dc.isRemoved)
            .map(async dc => {
                if (dc.isAdded) {
                    let result = await DashboardApi.addcard({ dashId, cardId: dc.card_id })
                    // mark isAdded because addcard doesn't record the position
                    return { ...result, col: dc.col, row: dc.row, sizeX: dc.sizeX, sizeY: dc.sizeY, series: dc.series, parameter_mappings: dc.parameter_mappings, isAdded: true }
                } else {
                    return dc;
                }
            }));

        // update modified cards
        await Promise.all(dashboard.ordered_cards
            .filter(dc => dc.card.isDirty)
            .map(async dc => CardApi.update(dc.card)));

        // update the dashboard itself
        if (dashboard.isDirty) {
            let { id, name, description, public_perms, parameters } = dashboard;
            dashboard = await DashboardApi.update({ id, name, description, public_perms, parameters });
        }

        // reposition the cards
        if (_.some(updatedDashcards, (dc) => dc.isDirty || dc.isAdded)) {
            let cards = updatedDashcards.map(({ id, row, col, sizeX, sizeY, series, parameter_mappings }) => ({ id, row, col, sizeX, sizeY, series, parameter_mappings }));
            var result = await DashboardApi.reposition_cards({ dashId, cards });
            if (result.status !== "ok") {
                throw new Error(result.status);
            }
        }

        // make sure that we've fully cleared out any dirty state from editing (this is overkill, but simple)
        dispatch(fetchDashboard(dashId, false, true)); // disable using query parameters when saving

        MetabaseAnalytics.trackEvent("Dashboard", "Update");

        return dashboard;
    };
});

export const deleteDashboard = createThunkAction(DELETE_DASHBOARD, function(dashId) {
    return async function(dispatch, getState) {
        await DashboardApi.delete({ dashId });
        MetabaseAnalytics.trackEvent("Dashboard", "Delete");
        return dashId;
    };
});

export const fetchRevisions = createThunkAction(FETCH_REVISIONS, function({ entity, id }) {
    return async function(dispatch, getState) {
        let revisions = await RevisionApi.list({ entity, id });
        return { entity, id, revisions };
    };
});

export const revertToRevision = createThunkAction(REVERT_TO_REVISION, function({ entity, id, revision_id }) {
    return async function(dispatch, getState) {
        await RevisionApi.revert({ entity, id, revision_id });
    };
});

export const setDashCardVisualizationSetting = createAction(SET_DASHCARD_VISUALIZATION_SETTING);

export const setEditingParameterId = createAction(SET_EDITING_PARAMETER_ID);
export const setParameterMapping = createThunkAction(SET_PARAMETER_MAPPING, (parameter_id, dashcard_id, card_id, target) =>
    (dispatch, getState) => {
        let parameter_mappings = getState().dashboard.dashcards[dashcard_id].parameter_mappings || [];
        parameter_mappings = parameter_mappings.filter(m => m.card_id !== card_id || m.parameter_id !== parameter_id);
        if (target) {
            parameter_mappings = parameter_mappings.concat({ parameter_id, card_id, target })
        }
        dispatch(setDashCardAttributes({ id: dashcard_id, attributes: { parameter_mappings }}));
    }
);

export const setParameterValue = createThunkAction(SET_PARAMETER_VALUE, (parameterId, value) =>
    (dispatch, getState) => {
        return { id: parameterId, value };
    }
)

// reducers

const isEditing = handleActions({
    [SET_EDITING_DASHBOARD]: { next: (state, { payload }) => payload }
}, false);

const cards = handleActions({
    [FETCH_CARDS]: { next: (state, { payload }) => ({ ...payload.entities.card }) }
}, {});

const cardList = handleActions({
    [FETCH_CARDS]: { next: (state, { payload }) => payload.result },
    [DELETE_CARD]: { next: (state, { payload }) => state }
}, null);

const dashboards = handleActions({
    [FETCH_DASHBOARD]: { next: (state, { payload }) => ({ ...state, ...payload.entities.dashboard }) },
    [SET_DASHBOARD_ATTRIBUTES]: {
        next: (state, { payload: { id, attributes } }) => ({
            ...state,
            [id]: { ...state[id], ...attributes, isDirty: true }
        })
    },
    [ADD_CARD_TO_DASH]: (state, { payload: dashcard }) => ({
        ...state, [dashcard.dashboard_id]: { ...state[dashcard.dashboard_id], ordered_cards: [...state[dashcard.dashboard_id].ordered_cards, dashcard.id] }
    }),
}, {});

const dashcards = handleActions({
    [FETCH_DASHBOARD]:  { next: (state, { payload }) => ({ ...state, ...payload.entities.dashcard }) },
    [SET_DASHCARD_ATTRIBUTES]: {
        next: (state, { payload: { id, attributes } }) => ({
            ...state,
            [id]: { ...state[id], ...attributes, isDirty: true }
        })
    },
    [SET_DASHCARD_VISUALIZATION_SETTING]: {
        next: (state, { payload: { id, setting, value } }) =>
            i.chain(state)
                .assocIn([id, "card", "visualization_settings"].concat(setting), value)
                .assocIn([id, "card", "isDirty"], true)
                .value()
    },
    [ADD_CARD_TO_DASH]: (state, { payload: dashcard }) => ({
        ...state,
        [dashcard.id]: { ...dashcard, isAdded: true, justAdded: true }
    }),
    [REMOVE_CARD_FROM_DASH]: (state, { payload: { dashcardId }}) => ({
        ...state,
        [dashcardId]: { ...state[dashcardId], isRemoved: true }
    }),
    [MARK_NEW_CARD_SEEN]: (state, { payload: dashcardId }) => ({
        ...state,
        [dashcardId]: { ...state[dashcardId], justAdded: false }
    })
}, {});

const editingParameterId = handleActions({
    [SET_EDITING_PARAMETER_ID]: { next: (state, { payload }) => payload }
}, null);

const revisions = handleActions({
    [FETCH_REVISIONS]: { next: (state, { payload: { entity, id, revisions } }) => ({ ...state, [entity+'-'+id]: revisions })}
}, {});

const dashcardData = handleActions({
    [FETCH_CARD_DATA]: { next: (state, { payload: { dashcard_id, card_id, result }}) => i.assocIn(state, [dashcard_id, card_id], result)}
}, {});

const cardDurations = handleActions({
    [FETCH_CARD_DURATION]: { next: (state, { payload: { id, result }}) => ({ ...state, [id]: result }) }
}, {});

const parameterValues = handleActions({
    [SET_PARAMETER_VALUE]: { next: (state, { payload: { id, value }}) => i.assoc(state, id, value) }
}, {});

export default combineReducers({
    isEditing,
    cards,
    cardList,
    dashboards,
    dashcards,
    editingParameterId,
    revisions,
    dashcardData,
    cardDurations,
    parameterValues
});
<|endoftext|>


<commit_before>/**
 *
 */

$("#existingapp").click(function() {

	loadPage("pages/apps-list.html");
});

//$("#newapp").on ("click", function () {
//	customer_index = -1;
//	$("#pageContent").load("pages/customer-entry-view.html", function () {
//
//		$(this).enhanceWithin(); /* apply styles */
//	});
//});

$( document ).ready(function() {

	//$(".current-title").text ("Choosing Application Type");

});

/*
 * starting  the slide  effect
 * */
$("#one").hide();
$("#two").hide();
$("#tree").hide();
var i=0;
$("#slider").on ("click", function () {
	//$("#two").show("slide", { direction: "right" }, 1200);
	//$( "#slider" ).hide();
	//$( "#slider" ).html( $("#two").html());
	if(i==0){
		$( "#slider" ).html( $("#one").html());
		//$("#one").show("slide", { direction: "left" }, 3000);
	i=i+1;
	return -1;
	}
	if(i==1){
		$( "#slider" ).html( $("#two").html());
		i=i+1;
		return -1;
		}
	if(i==3){
		$( "#slider" ).html( $("#tree").html());
		i=0;
		return -1;
		}
});

$("#mortgages").click(function() {

loadPage("../pages/mortgage-center-view.html");

});

<commit_msg>home-view.js fixed links<commit_after>/**
 *
 */

$("#existingapp").click(function() {

	loadPage("apps-list.html");
});

//$("#newapp").on ("click", function () {
//	customer_index = -1;
//	$("#pageContent").load("pages/customer-entry-view.html", function () {
//
//		$(this).enhanceWithin(); /* apply styles */
//	});
//});

$( document ).ready(function() {

	//$(".current-title").text ("Choosing Application Type");

});

/*
 * starting  the slide  effect
 * */
$("#one").hide();
$("#two").hide();
$("#tree").hide();
var i=0;
$("#slider").on ("click", function () {
	//$("#two").show("slide", { direction: "right" }, 1200);
	//$( "#slider" ).hide();
	//$( "#slider" ).html( $("#two").html());
	if(i==0){
		$( "#slider" ).html( $("#one").html());
		//$("#one").show("slide", { direction: "left" }, 3000);
	i=i+1;
	return -1;
	}
	if(i==1){
		$( "#slider" ).html( $("#two").html());
		i=i+1;
		return -1;
		}
	if(i==3){
		$( "#slider" ).html( $("#tree").html());
		i=0;
		return -1;
		}
});

$("#mortgages").click(function() {

loadPage("mortgage-center-view.html");

});

<|endoftext|>


<commit_before>/**
 *
 * check of bank and card
 *
 *
 *
 * 20150617
 */

function Log(msg){
	msg = msg || 'empty';
	if(envirment=="production"){

	} else {
		console.log(msg);
	}

}

(function($, undefined){
	//生产类
	var Pub = function(el, op){
		this.$el = $(el);

		this.boxFlag = 0;//标志卡号合法之后，下面层出现与否
		this.iFlag = 1;//标志所有数据合法时，提交按钮提交次数
		this.nameInput = this.$el.find('[node-type="LoanNameInput"]');
		this.cardInput = this.$el.find('[node-type="LoanCardInput"]');
		this.submitBtn = this.$el.find('[node-type="LoanSubmitBtn"]');
		this.bankCardIcon = this.$el.find('[node-type="LoanBankCardIcon"]');
		this.idCardInput = this.$el.find('[node-type="LoanIdCardInput"]');
		this.mobileIcon = this.$el.find('[node-type="LoanMobileIcon"]');
		this.mobileInput = this.$el.find('[node-type="LoanMobileInput"]');
		this.agreement = $("#agreement-btn");
		this.agreeClose = $("#close10");
		this.agree= $("#agreement");
		this.imbox = $("#imbox");
		this.bankName = $("#bank_name");
		this.mask = $("#masker");
		this.iconOption = $(".icon-option");
		this._addEvents();
	}

	Pub.prototype = {
		//所有事件绑定
		_addEvents : function(){
			var sthis=this;

			/*判断信息完整*/
			this.$el.find("input").on("input", function(){
				var _this = this;
				/*Log('AAA');*/
				setTimeout(function(){
					sthis._fnComplete();
					var Id = $(_this).attr("id");
					if(Id=="ID_card"){
						var idCard_val = $(_this).val();
						_this.value = sthis._fnIdentity(idCard_val);
					}
				},500)
			})

			/*卡号输入判断*/
			this.cardInput.on("keyup", function(){
				var card_id=this.value;
				this.value=sthis._fnCard(card_id);
			})
			/*身份证号输入判断*/
			this.idCardInput.on("keyup", function(){
				var idCard_val = this.value;
				this.value = sthis._fnIdentity(idCard_val);
				if(this.value==0){
					$(this).siblings().hide();
				}else{
					$(this).siblings().show();
				}
			})
			/*icon单机操作*/
			this.$el.find(".icon-option").on("click", function(){
				var icon_this=this;
				sthis._fnIcon(icon_this);
			})

			/*失去焦点隐藏icon*/
			this.$el.find("input").on("focus", function(){
				sthis.iconOption.hide();
				if(this.id=="ID_card"){
					if(this.value.length > 1){
						$(this).siblings().show();
					}
				}else{
					$(this).siblings().show();
				}
			})
			/*手机号输入判断*/
			this.mobileInput.on("keyup",function(){
				var mobile_val = this.value;
				this.value = sthis._fnModile(mobile_val);
			})

			/*提交按钮*/
			this.submitBtn.on("click",function(){

				var zCard = $("#bank_card").val();
				var zIdCard = $("#ID_card").val();
				var zMobile = $("#mobile").val();
				var zName = $("#name").val();
				sthis._fnSubmit(zCard,zIdCard,zMobile,zName);

			})
			/*协议*/
			this.agreement.on("click",function(){
				var wheight = $(window).height();
					wwidth = $(window).width();
					agreeList = $("#agree_list");

				/*sthis.agree.css("left",-wwidth);*/
				agreeList.height(wheight/6*4);
				sthis.agree.css("top",wheight/8);
				sthis._fnGetAgree();
				sthis.mask.show();
				sthis.agree.show();
				sthis.agree.css("left",0);
			})

			this.agreeClose.on("click",function(){
				sthis.mask.hide();
				sthis.agree.hide();
			})

		},
		//卡号处理
		_fnCard : function(card_id){
			card_id = card_id.replace(/\D/g,'').replace(/....(?!$)/g,'$& ');
			var b_len = card_id.length,
				bRIcon = $("#bRIcon");
			if(b_len==0){
				bRIcon.addClass("active").show().siblings().hide();
				this.imbox.hide();
				this.bankName.hide();
			}else if(b_len==1){
				bRIcon.removeClass("active").hide().siblings().show().removeClass("active");
			}else if(b_len==18){

			}else if(b_len==19){
				var bank_type=TOOLS.isBank(card_id.replace(/\s+/g,""));
				if(bank_type){
					this.boxFlag=1;
					this.imbox.show();
				}
			}else if(b_len<12){
				this.imbox.hide();
				this.boxFlag=0;
				this.bankName.hide();
			}else if(b_len>=12){
				var bank_type=TOOLS.isBank(card_id.replace(/\s+/g,""));
				if(bank_type){
					this.bankName.find("span").removeClass();
					this.bankName.find("span").addClass("bankName "+bank_type);
					this.bankName.show();

				}
			}
			return card_id;
		},
		//身份证处理
		_fnIdentity : function(idCard_val){
			idCard_val=idCard_val.replace(/[^\d|xX]/g,"");
			return idCard_val;
		},
		//手机号验证
		_fnModile : function(mobile_val){
			mobile_val = mobile_val.replace(/\D/g,'');
			var m_len = mobile_val.length;
				mRIcon = $("#mRIcon");

			if(m_len==0){
				mRIcon.addClass("active").show().siblings().hide();
			}else if(m_len==1){
				mRIcon.removeClass("active").hide().siblings().show().removeClass("active");
			}

			return mobile_val;
		},
		//表单处理
		_fnComplete : function(){
			var ipt=this.$el.find("input"),
				next=$("#next2"),
				ipt_flag=1,
				ipt_len=4;

			if(this.boxFlag==0){
				ipt_len=2;
			}
			for(var i=0; i<ipt_len; i++){
				if(ipt[i].value.length == 0){
					ipt_flag=0;
				}
			}
			if(ipt_flag==1){
				next.removeAttr("disabled");
			}else{
				next.attr("disabled","disabled");
			}
		},
		//icon处理
		_fnIcon : function(icon_this){
			var intype = $(icon_this).attr("node-type");
			var inIs = $(icon_this).find("i");
			var inIsD = $(inIs[0]).css("display");
			var next=$("#next2");
			if(inIs.hasClass("icons-infoNew active")){
				if(inIs.is(".icons-infoNew") && intype=="bank_card"){
					MessageBox.alert({type:"message",txt:CS.ERRORMSG["EFFECTIVEBANK"]});
				}else{
					MessageBox.alert({type:"message",txt:CS.ERRORMSG["AVAILABLEPHONENUMBER"]});
				}
			}else{
				if(inIs.is(".icons-infoNew")){
					$('#'+intype).val("");
					$(inIs[0]).addClass("active").show();
					$(inIs[1]).hide();
					if(intype=="bank_card"){
						this.imbox.hide();
						this.bankName.hide();
						this.idCardInput.val("");
						this.mobileInput.val("");
					}
					next.attr("disabled","disabled");
				}else{
					$('#'+intype).val("");
					$(inIs[0]).parent().hide();
					next.attr("disabled","disabled");
				}
			}
		},

		/*获取协议*/
		_fnGetAgree : function(){
			var now = new Date();
				year = now.getFullYear();
				month = now.getMonth()+1;
				day = now.getDate();
				name = $("#name").val();
				id_card = $("#ID_card").val();
				bank_card = $("#bank_card").val();
			$("#year").html(year);
			$("#year1").html(year);
			$("#month").html(month);
			$("#month1").html(month);
			$("#day").html(day);
			$("#day1").html(day);
			$("#c_id").html(id_card);
			$("#c_bid").html(bank_card);
			$("#c_name").html(name);
		},

		//提交按钮处理
		_fnSubmit : function(zCard,zIdCard,zMobile,zName){
			var zCard_len = zCard.length,
				age = TOOLS.isIdentity(zIdCard),
				phone = TOOLS.isMobile(zMobile),
				bcflag = TOOLS.isCard(zCard.replace(/\s+/g,"")),
				bcflag2 = TOOLS.isBank(zCard.replace(/\s+/g,"")),
				zzCard = zCard.replace(/\s+/g,""),
				smBtn = $("#next2");

			if(this.boxFlag){
				if(zCard_len != 19 && zCard_len == 23 || zCard_len != 23 && zCard_len == 19){
					if(bcflag){
						if(age){
							if(age>30 || age<16){
								MessageBox.alert({type:"common",txt:CS.ERRORMSG["AGENOTRANGEERROR"]});
							}else{
								if(phone){
									/*提交*/
									if(smBtn.hasClass("disabled")) return false;
									smBtn.addClass('disabled');
									TOOLS.ajax({
										url:"./index.php?r=loan/verify",
										data:{name:zName,bank_card:zzCard,id_card:zIdCard,mobile:zMobile},
										dataType:"json",
										type:"get",
										fnSuccess:function(data){
											console.log(data);
											if(data.stat == "1"){
												window.location="http://www.baidu.com";
											}else if(data.stat == "2"){
												if(data.verify_times == "2"){
													MessageBox.alert({type:"message",txt:CS.ERRORMSG["TWOCHANCE"],cls:true});
													smBtn.removeClass("disabled");
												}else if(data.verify_times == "1"){
													MessageBox.alert({type:"message",txt:CS.ERRORMSG["ONECHANCE"],cls:true});
													smBtn.removeClass("disabled");
												}else if(data.verify_times == "0"){
													alert("没机会了");
												}
											}else{
												alert("系统错误");
											}

										},
										fnError:function(){}
									});

								}else{
									MessageBox.alert({type:"common",txt:CS.ERRORMSG["MOBILEERROR"]});
								}
							}
						}else{
							MessageBox.alert({type:"common",txt:CS.ERRORMSG["IDENTITYERROR"]});
						}
					}else{
						MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
					}

				}else{
					MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
				}
			}else{
				if(zCard_len != 19 && zCard_len == 23 || zCard_len != 23 && zCard_len == 19){
					if(bcflag){
						if(bcflag2){
						}else{
							MessageBox.alert({type:"common",txt:CS.ERRORMSG["NOBANKERROR"]});
						}
					}else{
						MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
					}
				}else{
					MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
				}
			}

		}
	}

	$.fn.PUB = function(options){
		return this.each(function(){
			var $this = $(this) ,
				data = $this.data('PUB');

	          if (!data) {
	        	  $this.data('PUB', (data = new Pub(this, options)))
	          }

	          if (typeof options == 'string') data[options](setData);
		});
	}
})(window.jQuery||window.Zepto)<commit_msg>UPDATE<commit_after>/**
 *
 * check of bank and card
 *
 *
 *
 * 20150617
 */

function Log(msg){
	msg = msg || 'empty';
	if(envirment=="production"){

	} else {
		console.log(msg);
	}

}

(function($, undefined){
	//生产类
	var Pub = function(el, op){
		this.$el = $(el);

		this.boxFlag = 0;//标志卡号合法之后，下面层出现与否
		this.iFlag = 1;//标志所有数据合法时，提交按钮提交次数
		this.nameInput = this.$el.find('[node-type="LoanNameInput"]');
		this.cardInput = this.$el.find('[node-type="LoanCardInput"]');
		this.submitBtn = this.$el.find('[node-type="LoanSubmitBtn"]');
		this.bankCardIcon = this.$el.find('[node-type="LoanBankCardIcon"]');
		this.idCardInput = this.$el.find('[node-type="LoanIdCardInput"]');
		this.mobileIcon = this.$el.find('[node-type="LoanMobileIcon"]');
		this.mobileInput = this.$el.find('[node-type="LoanMobileInput"]');
		this.agreement = $("#agreement-btn");
		this.agreeClose = $("#close10");
		this.agree= $("#agreement");
		this.imbox = $("#imbox");
		this.bankName = $("#bank_name");
		this.mask = $("#masker");
		this.iconOption = $(".icon-option");
		this._addEvents();
	}

	Pub.prototype = {
		//所有事件绑定
		_addEvents : function(){
			var sthis=this;

			/*判断信息完整*/
			this.$el.find("input").on("input", function(){
				var _this = this;
				/*Log('AAA');*/
				setTimeout(function(){
					sthis._fnComplete();
					var Id = $(_this).attr("id");
					if(Id=="ID_card"){
						var idCard_val = $(_this).val();
						_this.value = sthis._fnIdentity(idCard_val);
					}
				},500)
			})

			/*卡号输入判断*/
			this.cardInput.on("keyup", function(){
				var card_id=this.value;
				this.value=sthis._fnCard(card_id);
			})
			/*身份证号输入判断*/
			this.idCardInput.on("keyup", function(){
				var idCard_val = this.value;
				this.value = sthis._fnIdentity(idCard_val);
				if(this.value==0){
					$(this).siblings().hide();
				}else{
					$(this).siblings().show();
				}
			})
			/*icon单机操作*/
			this.$el.find(".icon-option").on("click", function(){
				var icon_this=this;
				sthis._fnIcon(icon_this);
			})

			/*失去焦点隐藏icon*/
			this.$el.find("input").on("focus", function(){
				sthis.iconOption.hide();
				if(this.id=="ID_card"){
					if(this.value.length > 1){
						$(this).siblings().show();
					}
				}else{
					$(this).siblings().show();
				}
			})
			/*手机号输入判断*/
			this.mobileInput.on("keyup",function(){
				var mobile_val = this.value;
				this.value = sthis._fnModile(mobile_val);
			})

			/*提交按钮*/
			this.submitBtn.on("click",function(){

				var zCard = $("#bank_card").val();
				var zIdCard = $("#ID_card").val();
				var zMobile = $("#mobile").val();
				var zName = $("#name").val();
				sthis._fnSubmit(zCard,zIdCard,zMobile,zName);

			})
			/*协议*/
			this.agreement.on("click",function(){
				var wheight = $(window).height();
					wwidth = $(window).width();
					agreeList = $("#agree_list");

				/*sthis.agree.css("left",-wwidth);*/
				agreeList.height(wheight/6*4);
				sthis.agree.css("top",wheight/8);
				sthis._fnGetAgree();
				sthis.mask.show();
				sthis.agree.show();
				sthis.agree.css("left",0);
			})

			this.agreeClose.on("click",function(){
				sthis.mask.hide();
				sthis.agree.hide();
			})

		},
		//卡号处理
		_fnCard : function(card_id){
			card_id = card_id.replace(/\D/g,'').replace(/....(?!$)/g,'$& ');
			var b_len = card_id.length,
				bRIcon = $("#bRIcon");
			if(b_len==0){
				bRIcon.addClass("active").show().siblings().hide();
				this.imbox.hide();
				this.bankName.hide();
			}else if(b_len==1){
				bRIcon.removeClass("active").hide().siblings().show().removeClass("active");
			}else if(b_len==18){

			}else if(b_len==19){
				var bank_type=TOOLS.isBank(card_id.replace(/\s+/g,""));
				if(bank_type){
					this.boxFlag=1;
					this.imbox.show();
				}
			}else if(b_len<12){
				this.imbox.hide();
				this.boxFlag=0;
				this.bankName.hide();
			}else if(b_len>=12){
				var bank_type=TOOLS.isBank(card_id.replace(/\s+/g,""));
				if(bank_type){
					this.bankName.find("span").removeClass();
					this.bankName.find("span").addClass("bankName "+bank_type);
					this.bankName.show();

				}
			}
			return card_id;
		},
		//身份证处理
		_fnIdentity : function(idCard_val){
			idCard_val=idCard_val.replace(/[^\d|xX]/g,"");
			return idCard_val;
		},
		//手机号验证
		_fnModile : function(mobile_val){
			mobile_val = mobile_val.replace(/\D/g,'');
			var m_len = mobile_val.length;
				mRIcon = $("#mRIcon");

			if(m_len==0){
				mRIcon.addClass("active").show().siblings().hide();
			}else if(m_len==1){
				mRIcon.removeClass("active").hide().siblings().show().removeClass("active");
			}

			return mobile_val;
		},
		//表单处理
		_fnComplete : function(){
			var ipt=this.$el.find("input"),
				next=$("#next2"),
				ipt_flag=1,
				ipt_len=4;

			if(this.boxFlag==0){
				ipt_len=2;
			}
			for(var i=0; i<ipt_len; i++){
				if(ipt[i].value.length == 0){
					ipt_flag=0;
				}
			}
			if(ipt_flag==1){
				next.removeAttr("disabled");
			}else{
				next.attr("disabled","disabled");
			}
		},
		//icon处理
		_fnIcon : function(icon_this){
			var intype = $(icon_this).attr("node-type");
			var inIs = $(icon_this).find("i");
			var inIsD = $(inIs[0]).css("display");
			var next=$("#next2");
			if(inIs.hasClass("icons-infoNew active")){
				if(inIs.is(".icons-infoNew") && intype=="bank_card"){
					MessageBox.alert({type:"message",txt:CS.ERRORMSG["EFFECTIVEBANK"]});
				}else{
					MessageBox.alert({type:"message",txt:CS.ERRORMSG["AVAILABLEPHONENUMBER"]});
				}
			}else{
				if(inIs.is(".icons-infoNew")){
					$('#'+intype).val("");
					$(inIs[0]).addClass("active").show();
					$(inIs[1]).hide();
					if(intype=="bank_card"){
						this.imbox.hide();
						this.bankName.hide();
						this.idCardInput.val("");
						this.mobileInput.val("");
					}
					next.attr("disabled","disabled");
				}else{
					$('#'+intype).val("");
					$(inIs[0]).parent().hide();
					next.attr("disabled","disabled");
				}
			}
		},

		/*获取协议*/
		_fnGetAgree : function(){
			var now = new Date();
				year = now.getFullYear();
				month = now.getMonth()+1;
				day = now.getDate();
				name = $("#name").val();
				id_card = $("#ID_card").val();
				bank_card = $("#bank_card").val();
			$("#year").html(year);
			$("#year1").html(year);
			$("#month").html(month);
			$("#month1").html(month);
			$("#day").html(day);
			$("#day1").html(day);
			$("#c_id").html(id_card);
			$("#c_bid").html(bank_card);
			$("#c_name").html(name);
		},

		//提交按钮处理
		_fnSubmit : function(zCard,zIdCard,zMobile,zName){
			var zCard_len = zCard.length,
				age = TOOLS.isIdentity(zIdCard),
				phone = TOOLS.isMobile(zMobile),
				bcflag = TOOLS.isCard(zCard.replace(/\s+/g,"")),
				bcflag2 = TOOLS.isBank(zCard.replace(/\s+/g,"")),
				zzCard = zCard.replace(/\s+/g,""),
				smBtn = $("#next2");

			if(this.boxFlag){
				if(zCard_len != 19 && zCard_len == 23 || zCard_len != 23 && zCard_len == 19){
					if(bcflag){
						if(age){
							if(age>30 || age<16){
								MessageBox.alert({type:"common",txt:CS.ERRORMSG["AGENOTRANGEERROR"]});
							}else{
								if(phone){
									/*提交*/
									if(smBtn.hasClass("disabled")) return false;
									smBtn.addClass('disabled');
									TOOLS.ajax({
										url:"./index.php?r=loan/verify",
										data:{name:zName,bank_card:zzCard,id_card:zIdCard,mobile:zMobile},
										dataType:"json",
										type:"get",
										fnSuccess:function(data){
											if(data.stat == "1"){
												window.location="http://www.baidu.com";
											}else if(data.stat == "2"){
												if(data.verify_times == "2"){
													MessageBox.alert({type:"message",txt:CS.ERRORMSG["TWOCHANCE"],cls:true});
													smBtn.removeClass("disabled");
												}else if(data.verify_times == "1"){
													MessageBox.alert({type:"message",txt:CS.ERRORMSG["ONECHANCE"],cls:true});
													smBtn.removeClass("disabled");
												}else if(data.verify_times == "0"){
													alert("没机会了");
												}else{
													alert("111");
												}
											}else{
												alert("系统错误");
											}

										},
										fnError:function(){}
									});

								}else{
									MessageBox.alert({type:"common",txt:CS.ERRORMSG["MOBILEERROR"]});
								}
							}
						}else{
							MessageBox.alert({type:"common",txt:CS.ERRORMSG["IDENTITYERROR"]});
						}
					}else{
						MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
					}

				}else{
					MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
				}
			}else{
				if(zCard_len != 19 && zCard_len == 23 || zCard_len != 23 && zCard_len == 19){
					if(bcflag){
						if(bcflag2){
						}else{
							MessageBox.alert({type:"common",txt:CS.ERRORMSG["NOBANKERROR"]});
						}
					}else{
						MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
					}
				}else{
					MessageBox.alert({type:"common",txt:CS.ERRORMSG["BANKCARDERROR"]});
				}
			}

		}
	}

	$.fn.PUB = function(options){
		return this.each(function(){
			var $this = $(this) ,
				data = $this.data('PUB');

	          if (!data) {
	        	  $this.data('PUB', (data = new Pub(this, options)))
	          }

	          if (typeof options == 'string') data[options](setData);
		});
	}
})(window.jQuery||window.Zepto)<|endoftext|>


<commit_before>import Cookies from 'js-cookie'

export function fetch(method, url, req_body) {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest()
    request.open(method, url, true)
    request.setRequestHeader('X-CSRFToken', Cookies.get('csrftoken'))
    request.addEventListener('load', () => resolve(request.responseText))
    request.addEventListener('error', () => reject('Request Error: ', request.response))
    request.send(req_body)
  })
}

export function calculateSimilarity(item_answers, user_answers) {
    let points = 0,
        answers_count = 0,
        answers = user_answers

    // Similarity is 0% if there is no data about item answers
    if (!item_answers)
        return 0

    for (let answer_id in item_answers) {
        if (answers[answer_id] && answers[answer_id].answer) {
            let answer_points = Math.abs((answers[answer_id].answer + Number(item_answers[answer_id])) / 4)
            if (answers[answer_id].important) {
                answers_count += 2
                answer_points *= 2
            } else {
                answers_count++
            }
            points += answer_points
        }
    }

    return Math.round((points / answers_count)*100)
}

export function sortResults(results) {
    // Filter out old/inactive/renamed fractions which have 0 members.
    results.fractions = results.fractions.filter((fraction) => fraction.members_amount > 0)

    // Calculate fractions similarity
    results.fractions = results.fractions.map((fraction) => {
        fraction.similarity = calculateSimilarity(fraction.answers, results.user_answers || results.answers)
        return fraction
    })

    // Filter out not active mps (they don't have fraction)
    results.mps = results.mps.filter((mp) => mp.fraction_id)

    // Calculate mps similarity
    results.mps = results.mps.map((mp) => {
        mp.similarity = calculateSimilarity(mp.answers, results.user_answers || results.answers)
        return mp
    })

    // Sort fractions and mps by similarity (desc)
    function compare (a, b) {
        if (a.similarity > b.similarity)
            return -1

        if (a.similarity < b.similarity)
            return 1

        return 0
    }
    results.fractions.sort(compare)
    results.mps.sort(compare)

    return results
}
<commit_msg>Fix empty check in similarity calculations<commit_after>import Cookies from 'js-cookie'

export function fetch(method, url, req_body) {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest()
    request.open(method, url, true)
    request.setRequestHeader('X-CSRFToken', Cookies.get('csrftoken'))
    request.addEventListener('load', () => resolve(request.responseText))
    request.addEventListener('error', () => reject('Request Error: ', request.response))
    request.send(req_body)
  })
}

export function calculateSimilarity(item_answers, user_answers) {
    let points = 0,
        answers_count = 0,
        answers = user_answers

    // Similarity is 0% if there is no data about item answers
    if (!item_answers || !Object.keys(item_answers).length)
        return 0

    for (let answer_id in item_answers) {
        if (answers[answer_id] && answers[answer_id].answer) {
            let answer_points = Math.abs((answers[answer_id].answer + Number(item_answers[answer_id])) / 4)
            if (answers[answer_id].important) {
                answers_count += 2
                answer_points *= 2
            } else {
                answers_count++
            }
            points += answer_points
        }
    }

    return Math.round((points / answers_count)*100)
}

export function sortResults(results) {
    // Filter out old/inactive/renamed fractions which have 0 members.
    results.fractions = results.fractions.filter((fraction) => fraction.members_amount > 0)

    // Calculate fractions similarity
    results.fractions = results.fractions.map((fraction) => {
        fraction.similarity = calculateSimilarity(fraction.answers, results.user_answers || results.answers)
        return fraction
    })

    // Filter out not active mps (they don't have fraction)
    results.mps = results.mps.filter((mp) => mp.fraction_id)

    // Calculate mps similarity
    results.mps = results.mps.map((mp) => {
        mp.similarity = calculateSimilarity(mp.answers, results.user_answers || results.answers)
        return mp
    })

    // Sort fractions and mps by similarity (desc)
    function compare (a, b) {
        if (a.similarity > b.similarity)
            return -1

        if (a.similarity < b.similarity)
            return 1

        return 0
    }
    results.fractions.sort(compare)
    results.mps.sort(compare)

    return results
}
<|endoftext|>


<commit_before>/*  checkforcode.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com

    14 July, 2017
    https://github.com/GlassToeStudio
    http://glasstoestudio.weebly.com/
    https://twitter.com/GlassToeStudio

    ------------------------------------------------------------------------
    Systematically search through Discord comments to find unformatted Code.

        * Search is based in chars in codeElements array.
            * Default:  [';', '{', '}', ')']
        * Bot will parse the code line by line searchig for
        * code elements and keep track of which line are code
        * and which are plain text.
        * The bot will do his best to only format a true code
        * block, leaveing the plain text alone. One complte
        * the bot will add code block formattign aroind the
        * code block, with the current code lang 'csharp'.
        * The message will be posted anew as formatted code
        * and, if possible, the old message will be deleted.
        *
        * If the informatted code is posted in a channel with
        * 'help' in the tile, then the new messge is posted
        * there.
        * If the message is posted in any other chnannel, the
        * new message will be posted in #programing_help (if it exists)
        * If there is no programing_help channel, the message is
        * posted in the original channel.
        *
        * When a new message is posted in a new channel, the user
        * is notified that the post has been moved, with a link
        * to the channel. The new post in the new channel will
        * also mention the user, as an added bonus to help
        * the user navigate the newmessage.

    EXAMPLE-----------------------------------------------------------------

    THis is not code so it is not in the code block.

    ```csharp
    using UnityEngine;

    /// <summary>
    /// Class summary
    /// </summary>
    public class MyClass
    {
        /// <summary>   Summary of myFloat. </summary>
        public float myFloat;
        /// <summary>   Summary of myOtherFloat.</summary>
        public float myOtherFLoat;

        // Default constructor
        public MyClass(float _myFloat, float _myOtherFLoat)
        {
            this.myFloat = _myFloat;
            this.myOtherFLoat = _myOtherFLoat;
        }
    }
    ```

    THis is not code so it is not in the code block.

    ------------------------------------------------------------------------

    Discord Markdown 101 for more formatting guidelines:
    https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-?page=4
*/

const Discord = require('discord.js');
const GlassBot = require('../bot.js')
const bot = GlassBot.bot
const config = GlassBot.config

// Salt to taste
const codeElements = [';', '{', '}', ')'] // Could be in config
const codeLang = 'csharp'
const repostThreshold = 4

// Variables
var isFormatted = false
var totalLinesOfCode = 0
var firstLine = false
var lastLine = 0

// Lets begin
bot.on('message', message => {
    if(message.author.bot){
        /*
        THIS IS ALL EXPERIMENTAL
        */
        // If this is our reply to the user, delete after 10 seconds
        if(message.content.includes('Your unformatted code')){
            let usr = message.mentions
            for(let i = 0; i < usr.length; i++){
                console.log(message.mentions[i])
            }

            let chnl = (message.guild.channels.find("name", "programing_help") != null) ? message.guild.channels.find("name", "programing_help") : message.channel
            callNTimes(10, 1000, EditBotMessage, message, chnl, usr)
        }
        // END EXPERIMENTAL
        return
    }
    if(message.content.length > 1900) return
    InitVariables()

    let lines = message.content.split('\n')

    ParseMessage(lines)

    if(IsBadCode() && !isFormatted){

        lines[lastLine] = FormatLastLine(lines[lastLine])

        CreateNewMessage(lines, message)
    }
    return
});

// Loop through each line in message and check for
// code-like characters. If code formatting is found
// return, else keep checking.
function ParseMessage(lines){
    for(let i = 0; i < lines.length; i++){
        if(lines[i].search("```") >= 0){
            isFormatted = true
            return
        }else
            FindCodeElements(i, lines[i], lines)
    }
    return
}

// Checks the last character in a string to see of it machess a code-like character
function FindCodeElements(index, line, lines){
    let lineLength = line.length - 1
    for(let i = 0; i < codeElements.length; i++){
        if (line.charAt(lineLength).valueOf() == codeElements[i].valueOf()){
            if(!firstLine){
                lines[index] = FormatFirstLine(line)
                return
            }else{
                lastLine = index
                totalLinesOfCode += 1
                return
            }
        }
    }
    return
}

// Recreate the new message with code formatting included
function CreateNewMessage(lines, message){
    let newMessage = ""
    for(let j = 0; j < lines.length; j++)
       newMessage += lines[j] + '\n'

    PostNewMessage(message, newMessage)
}

// Post the formatted message in the appropriate channel
function PostNewMessage(message, newMessage){
    let channel = message.guild.channels.find("name", "programing_help")
    let isHelp = message.channel.name.indexOf('help') > 0

    if(channel != null && channel != message.channel && !isHelp){
        // TODO: Would like to add some color to this message
        message.reply(':nerd: __`Your unformatted code has been formatted and moved to`__ ' + channel + '. :nerd: \n\t*This message will self-destruct in 10 seconds*')
        channel.send(message.author + ' **★★ I have formatted your code and placed it here. Good Luck! ★★** ')
        channel.send(newMessage);
    }else{
        message.channel.send(message.author + ' **★★ I see you forgot to format your code... Let me help you. ★★** ')
        message.channel.send(newMessage)
    }

    DeleteOldMessage(message)
}

// Deletes the old unformatted messge if bot has permission
function DeleteOldMessage(message){
    let managePerms = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES')
    if(managePerms)
        message.delete()
    else
        message.channel.send('**`Tell the server\'s owner to grant me permission to delete your old message, thank\'s`** :wink:')
}

// Adds code formatting block start to the first line of code
function FormatFirstLine(inLine){
    firstLine = true
    return '```' + codeLang + '\n' + inLine
}

// Add formatting code bock end to the last line of code
function FormatLastLine(inLine){
    return inLine + '\n```'
}

// If total line of code is greater than repostThreshold return true
function IsBadCode(){
    return (totalLinesOfCode >= repostThreshold)
}

// Initalize variables
function InitVariables(){
    isFormatted = false
    totalLinesOfCode = 0;
    firstLine = false
    lastLine = 0
}

/*
THIS IS ALL EXPERIMENTAL
 */
function EditBotMessage(usr, message, channel, t){
    message.edit(usr + ':nerd: __`Your unformatted code has been formatted and moved to`__ ' + channel + '. :nerd: \n\t*This message will self-destruct in ' + t + ' seconds*')
}

/*
THIS IS ALL EXPERIMENTAL
 */
function callNTimes(n, time, fn, msg, chnl, usr) {
  function callFn() {
    if (--n < 0){
        msg.delete()
        .then(m => console.log(`Deleted message from ${m.author}`))
        .catch(console.error);
        return;
    }
    fn(usr, msg, chnl, n);
    setTimeout(callFn, time);
  }
  setTimeout(callFn, time);
}<commit_msg>global usr, not real good<commit_after>/*  checkforcode.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com

    14 July, 2017
    https://github.com/GlassToeStudio
    http://glasstoestudio.weebly.com/
    https://twitter.com/GlassToeStudio

    ------------------------------------------------------------------------
    Systematically search through Discord comments to find unformatted Code.

        * Search is based in chars in codeElements array.
            * Default:  [';', '{', '}', ')']
        * Bot will parse the code line by line searchig for
        * code elements and keep track of which line are code
        * and which are plain text.
        * The bot will do his best to only format a true code
        * block, leaveing the plain text alone. One complte
        * the bot will add code block formattign aroind the
        * code block, with the current code lang 'csharp'.
        * The message will be posted anew as formatted code
        * and, if possible, the old message will be deleted.
        *
        * If the informatted code is posted in a channel with
        * 'help' in the tile, then the new messge is posted
        * there.
        * If the message is posted in any other chnannel, the
        * new message will be posted in #programing_help (if it exists)
        * If there is no programing_help channel, the message is
        * posted in the original channel.
        *
        * When a new message is posted in a new channel, the user
        * is notified that the post has been moved, with a link
        * to the channel. The new post in the new channel will
        * also mention the user, as an added bonus to help
        * the user navigate the newmessage.

    EXAMPLE-----------------------------------------------------------------

    THis is not code so it is not in the code block.

    ```csharp
    using UnityEngine;

    /// <summary>
    /// Class summary
    /// </summary>
    public class MyClass
    {
        /// <summary>   Summary of myFloat. </summary>
        public float myFloat;
        /// <summary>   Summary of myOtherFloat.</summary>
        public float myOtherFLoat;

        // Default constructor
        public MyClass(float _myFloat, float _myOtherFLoat)
        {
            this.myFloat = _myFloat;
            this.myOtherFLoat = _myOtherFLoat;
        }
    }
    ```

    THis is not code so it is not in the code block.

    ------------------------------------------------------------------------

    Discord Markdown 101 for more formatting guidelines:
    https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-?page=4
*/

const Discord = require('discord.js');
const GlassBot = require('../bot.js')
const bot = GlassBot.bot
const config = GlassBot.config

// Salt to taste
const codeElements = [';', '{', '}', ')'] // Could be in config
const codeLang = 'csharp'
const repostThreshold = 4

// Variables
var isFormatted = false
var totalLinesOfCode = 0
var firstLine = false
var lastLine = 0

// Real hacky way to do this.
var usr

// Lets begin
bot.on('message', message => {
    if(message.author.bot){
        /*
        THIS IS ALL EXPERIMENTAL
        */
        // If this is our reply to the user, delete after 10 seconds
        if(message.content.includes('Your unformatted code')){
            let usr = message.mentions
            for(let i = 0; i < usr.length; i++){
                console.log(message.mentions[i])
            }

            let chnl = (message.guild.channels.find("name", "programing_help") != null) ? message.guild.channels.find("name", "programing_help") : message.channel
            callNTimes(10, 1000, EditBotMessage, message, chnl, usr)
        }
        // END EXPERIMENTAL
        return
    }
    if(message.content.length > 1900) return
    InitVariables()

    let lines = message.content.split('\n')

    ParseMessage(lines)

    if(IsBadCode() && !isFormatted){

        lines[lastLine] = FormatLastLine(lines[lastLine])

        CreateNewMessage(lines, message)
    }
    return
});

// Loop through each line in message and check for
// code-like characters. If code formatting is found
// return, else keep checking.
function ParseMessage(lines){
    for(let i = 0; i < lines.length; i++){
        if(lines[i].search("```") >= 0){
            isFormatted = true
            return
        }else
            FindCodeElements(i, lines[i], lines)
    }
    return
}

// Checks the last character in a string to see of it machess a code-like character
function FindCodeElements(index, line, lines){
    let lineLength = line.length - 1
    for(let i = 0; i < codeElements.length; i++){
        if (line.charAt(lineLength).valueOf() == codeElements[i].valueOf()){
            if(!firstLine){
                lines[index] = FormatFirstLine(line)
                return
            }else{
                lastLine = index
                totalLinesOfCode += 1
                return
            }
        }
    }
    return
}

// Recreate the new message with code formatting included
function CreateNewMessage(lines, message){
    let newMessage = ""
    for(let j = 0; j < lines.length; j++)
       newMessage += lines[j] + '\n'

    PostNewMessage(message, newMessage)
}

// Post the formatted message in the appropriate channel
function PostNewMessage(message, newMessage){
    let channel = message.guild.channels.find("name", "programing_help")
    let isHelp = message.channel.name.indexOf('help') > 0

    if(channel != null && channel != message.channel && !isHelp){
        // TODO: Would like to add some color to this message
        usr = message.author
        message.reply(':nerd: __`Your unformatted code has been formatted and moved to`__ ' + channel + '. :nerd: \n\t*This message will self-destruct in 10 seconds*')
        channel.send(message.author + ' **★★ I have formatted your code and placed it here. Good Luck! ★★** ')
        channel.send(newMessage);
    }else{
        message.channel.send(message.author + ' **★★ I see you forgot to format your code... Let me help you. ★★** ')
        message.channel.send(newMessage)
    }

    DeleteOldMessage(message)
}

// Deletes the old unformatted messge if bot has permission
function DeleteOldMessage(message){
    let managePerms = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES')
    if(managePerms)
        message.delete()
    else
        message.channel.send('**`Tell the server\'s owner to grant me permission to delete your old message, thank\'s`** :wink:')
}

// Adds code formatting block start to the first line of code
function FormatFirstLine(inLine){
    firstLine = true
    return '```' + codeLang + '\n' + inLine
}

// Add formatting code bock end to the last line of code
function FormatLastLine(inLine){
    return inLine + '\n```'
}

// If total line of code is greater than repostThreshold return true
function IsBadCode(){
    return (totalLinesOfCode >= repostThreshold)
}

// Initalize variables
function InitVariables(){
    isFormatted = false
    totalLinesOfCode = 0;
    firstLine = false
    lastLine = 0
}

/*
THIS IS ALL EXPERIMENTAL
 */
function EditBotMessage(usr, message, channel, t){
    message.edit(usr + ':nerd: __`Your unformatted code has been formatted and moved to`__ ' + channel + '. :nerd: \n\t*This message will self-destruct in ' + t + ' seconds*')
}

/*
THIS IS ALL EXPERIMENTAL
 */
function callNTimes(n, time, fn, msg, chnl, usr) {
  function callFn() {
    if (--n < 0){
        msg.delete()
        .then(m => console.log(`Deleted message from ${m.author}`))
        .catch(console.error);
        return;
    }
    fn(usr, msg, chnl, n);
    setTimeout(callFn, time);
  }
  setTimeout(callFn, time);
}<|endoftext|>


<commit_before>/*---------------------------------------------------------
 * OpenERP base_graph
 *---------------------------------------------------------*/

openerp.base_graph = function (openerp) {
QWeb.add_template('/base_graph/static/src/xml/base_graph.xml');
openerp.base.views.add('graph', 'openerp.base_graph.GraphView');
openerp.base_graph.GraphView = openerp.base.Controller.extend({

    init: function(view_manager, session, element_id, dataset, view_id) {
        this._super(session, element_id);
        this.view_manager = view_manager;
        this.dataset = dataset;
        this.model = this.dataset.model;
        this.view_id = view_id;
    },
    do_show: function () {
        // TODO: re-trigger search
        this.$element.show();
    },
    do_hide: function () {
        this.$element.hide();
    },
    start: function() {
        this.rpc("/base_graph/graphview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
    },
    on_loaded: function(data) {
        this.all_fields = data.all_fields;
        this.fields_view = data.fields_view;
        this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
        this.view_id = this.fields_view.view_id;
        this.chart = this.fields_view.arch.attrs.type || 'pie';
        this.fields = this.fields_view.fields;
        this.chart_info_fields = [];
        this.operator_field = '';
        this.operator_field_one = '';
        this.operator = [];
        this.group_field = '';
        this.orientation = this.fields_view.arch.attrs.orientation || '';

        _.each(this.fields_view.arch.children, function (field) {
            if (field.attrs.operator) {
                this.operator.push(field.attrs.name);
            }
            else if (field.attrs.group) {
                this.group_field = field.attrs.name;
            }
            else {
                this.chart_info_fields.push(field.attrs.name);
            }
        }, this);

        this.operator_field = this.operator[0];
        if(this.operator.length > 1){
            this.operator_field_one = this.operator[1];
        }
        if(this.operator == ''){
            this.operator_field = this.chart_info_fields[1];
        }
        this.chart_info = this.chart_info_fields[0];
        this.x_title = this.fields[this.chart_info_fields[0]]['string'];
        this.y_title = this.fields[this.operator_field]['string'];
        this.load_chart();
    },

    load_chart: function(data) {
        var self = this;
        if(data){
            this.x_title = this.all_fields[this.chart_info_fields]['string'];
            this.y_title = this.all_fields[this.operator_field]['string'];
            self.schedule_chart(data);
        }else{
            this.dataset.read_ids(
                this.dataset.ids,
                {},
                function(res) {
                    self.schedule_chart(res);
                }
            );
        }
    },

    schedule_chart: function(results) {
        this.$element.html(QWeb.render("GraphView", {"fields_view": this.fields_view, "chart": this.chart,'view_id': this.view_id}));
        if (results.length){
            _.each(results, function (result) {
                _.each(result, function (field_value, field_name) {
                    if (typeof field_value == 'object') {
                        result[field_name] = field_value[field_value.length - 1];
                    }
                    if (typeof field_value == 'string'){
                        var choices = this.all_fields[field_name]['selection'];
                        _.each(choices, function (choice) {
                            if(field_value == choice[0]){
                               result[field_name] = choice;
                            }
                        });
                    }
                }, this);
            }, this);

            var opration_fld = {};
            _.each(results, function (result) {
                var gen_key = result[this.chart_info_fields]+"_"+result[this.group_field];
                var map_val = {};
                if (opration_fld[gen_key] == undefined){
                    map_val[this.operator_field] = result[this.operator_field];
                    if (this.operator.length > 1){
                        map_val[this.operator_field_one] = result[this.operator_field_one];
                    }
                    map_val[this.chart_info_fields] = result[this.chart_info_fields];
                    if (this.group_field){
                        map_val[this.group_field] = (typeof result[this.group_field] == 'object')?result[this.group_field][1]:result[this.group_field];
                    }
                } else {
                    map_val = opration_fld[gen_key];
                    map_val[this.operator_field] += result[this.operator_field];
                    if (this.operator.length > 1){
                        map_val[this.operator_field_one] += result[this.operator_field_one];
                    }
                }
                opration_fld[gen_key] = map_val;
            }, this);

            var graph_data = [];
            _.each(opration_fld, function (column_data) {
                graph_data.push(column_data);
            });

            if(this.chart == 'bar') {
                return this.schedule_bar(graph_data);
            } else if(this.chart == "pie") {
                return this.schedule_pie(graph_data);
            }
        }
    },

    schedule_bar: function(results) {
        var self = this;
        var view_chart = '';
        var xystr = {};
        var group_list = [];
        var newkey = '', newkey_one;

        var COLOR_PALETTE = ['#cc99ff', '#ccccff', '#48D1CC', '#CFD784', '#8B7B8B', '#75507b', '#b0008c', '#ff0000', '#ff8e00', '#9000ff', '#0078ff', '#00ff00', '#e6ff00', '#ffff00',
                     '#905000', '#9b0000', '#840067', '#9abe00', '#ffc900', '#510090', '#0000c9', '#009b00',
                     '#75507b', '#3465a4', '#73d216', '#c17d11', '#edd400', '#fcaf3e', '#ef2929', '#ff00c9',
                     '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f', '#f57900', '#cc0000', '#d400a8'];

        if(self.group_field && (this.operator.length <= 1)){
            view_chart = self.orientation == 'horizontal'? 'stackedBarH' : 'stackedBar';
        }else{
            view_chart = self.orientation == 'horizontal'? 'barH' : 'bar';
        }

        _.each(results, function (result) {
            if (self.group_field && (this.operator.length <= 1)){
                newkey = result[self.group_field].split(' ').join('_');
            } else {
                newkey = "val";
            }

            if (jQuery.inArray(newkey, group_list) == -1){
                group_list.push(newkey);
                if(this.operator.length > 1){
                    newkey_one = "val1";
                    group_list.push(newkey_one);
                }
            }
        }, this);

        _.each(results, function (result) {
            var xystring = result[self.chart_info_fields],
                xyname = {};
            if (self.group_field && (self.operator.length <= 1)){
                newkey = result[self.group_field].split(' ').join('_');
            }else{
                newkey = "val";
            }
            if (xystr[xystring] == undefined){
                xyname[self.chart_info_fields] = xystring;
                _.each(group_list, function (group) {
                    xyname[group] = 0.0001;
                });
            } else {
                xyname = xystr[xystring];
            }
            xyname[newkey] = result[self.operator_field];
            if (self.operator.length > 1){
                xyname[newkey_one] = result[self.operator_field_one];
            }
            xystr[xystring] = xyname;
        });

        //for legend color
        var grp_color = _.map(group_list, function (group_legend, index) {
            var legend = {color: COLOR_PALETTE[index]};

            if (group_legend == "val"){
                legend['text'] = self.fields[self.operator_field]['string']
            }else if(group_legend == "val1"){
                legend['text'] = self.fields[self.operator_field_one]['string']
            }else{
                legend['text'] = group_legend;
            }
            return legend;
        });

        //for axis's value and title
        var temp_ax = {
            template: self.chart_info_fields,
            title: ""+self.x_title+""
        };
        var oth_ax = {
            lines: true,
            title: ""+self.y_title+""
        };

        var x_ax, y_ax;
        if (self.orientation == 'horizontal'){
             x_ax = oth_ax;
             y_ax = temp_ax;
        }else{
             x_ax = temp_ax;
             y_ax = oth_ax;
        }

        var bar_chart = new dhtmlxchartChart({
            view: view_chart,
            container: self.view_id+"-barchart",
            value:"#"+group_list[0]+"#",
            gradient: "3d",
            border: false,
            width: 1024,
            radius: 0,
            color:grp_color[0]['color'],
            origin:0,
            xAxis:{
                template:function(obj){
                    if(x_ax['template']){
                        var val = obj[x_ax['template']];
                        val = (typeof val == 'object')?val[1]:(!val?'Undefined':val);
                        return val;
                    }else{
                        return obj;
                    }
                },
                title:x_ax['title'],
                lines:x_ax['lines']
            },
            yAxis:{
                template:function(obj){
                    if(y_ax['template']){
                        var vals = obj[y_ax['template']];
                        vals = (typeof vals == 'object')?vals[1]:(!vals?'Undefined':vals);
                        return vals;
                    }else{
                        return obj;
                    }
                },
                title:y_ax['title'],
                lines: y_ax['lines']
            },
            padding: {
                left: 75
            },
            legend: {
                values: grp_color,
                align:"left",
                valign:"top",
                layout: "x",
                marker:{
                    type:"round",
                    width:12
                }
            }
        });
        for (var m = 1; m<group_list.length;m++){
            bar_chart.addSeries({
                value: "#"+group_list[m]+"#",
                color: grp_color[m]['color']
            });
        }
        bar_chart.parse(_.values(xystr), "json");
        bar_chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(bar_chart.get(id));
        });
    },
    schedule_pie: function(result) {
        var self = this;
        var chart =  new dhtmlxchartChart({
            view:"pie3D",
            container:self.view_id+"-piechart",
            value:"#"+self.operator_field+"#",
            pieInnerText:function(obj) {
                var sum = chart.sum("#"+self.operator_field+"#");
                var val = obj[self.operator_field] / sum * 100 ;
                return Math.round(val * 10)/10 + "%";
            },
            gradient:"3d",
            height: 20,
            radius: 200,
            legend: {
                width: 300,
                align:"left",
                valign:"top",
                layout: "x",
                marker:{
                    type:"round",
                    width:12
                },
                template:function(obj){
                    var val = obj[self.chart_info_fields];
                    val = (typeof val == 'object')?val[1]:val;
                    return val;
                }
            }
        });
        chart.parse(result,"json");
        chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(chart.get(id));
        });
    },
    open_list_view : function (id){
        var self = this;
        id = id[self.chart_info_fields];
        if (typeof id == 'object'){
            id = id[0];
        }
        if(this.view_manager.action.context){
           this.view_manager.action.context = {};
        }
        if(!this.view_manager.action.domain) {
            this.view_manager.action.domain = [[self.chart_info_fields, '=', id],['id','in',self.dataset.ids]];
        } else {
            this.view_manager.action.domain.push([self.chart_info_fields, '=', id],['id','in',self.dataset.ids]);
        }
        var action_manager = new openerp.base.ActionManager(this.view_manager.session, this.view_manager.element_id);
        action_manager.start();
        action_manager.do_action(this.view_manager.action);
    },
    do_search: function(domains, contexts, groupbys) {
        var self = this;
        this.rpc('/base/session/eval_domain_and_context', {
            domains: domains,
            contexts: contexts,
            group_by_seq: groupbys
        }, function (results) {
            // TODO: handle non-empty results.group_by with read_group
            if(results.group_by  && results.group_by != ''){
                self.chart_info_fields = results.group_by[0];
            }else{
                self.chart_info_fields = self.chart_info;
            }
            self.dataset.context = self.context = results.context;
            self.dataset.domain = self.domain = results.domain;
            self.dataset.read_slice({}, 0, self.limit,function(response){
                self.load_chart(response);
            });
        });
    }

});

// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them

};

// vim:et fdc=0 fdl=0:
<commit_msg>[IMP] move COLOR_PALETTE to toplevel constant, so it does not have to be recreated every time we go through the corresponding function<commit_after>/*---------------------------------------------------------
 * OpenERP base_graph
 *---------------------------------------------------------*/

openerp.base_graph = function (openerp) {
var COLOR_PALETTE = [
    '#cc99ff', '#ccccff', '#48D1CC', '#CFD784', '#8B7B8B', '#75507b',
    '#b0008c', '#ff0000', '#ff8e00', '#9000ff', '#0078ff', '#00ff00',
    '#e6ff00', '#ffff00', '#905000', '#9b0000', '#840067', '#9abe00',
    '#ffc900', '#510090', '#0000c9', '#009b00', '#75507b', '#3465a4',
    '#73d216', '#c17d11', '#edd400', '#fcaf3e', '#ef2929', '#ff00c9',
    '#ad7fa8', '#729fcf', '#8ae234', '#e9b96e', '#fce94f', '#f57900',
    '#cc0000', '#d400a8'];

QWeb.add_template('/base_graph/static/src/xml/base_graph.xml');
openerp.base.views.add('graph', 'openerp.base_graph.GraphView');
openerp.base_graph.GraphView = openerp.base.Controller.extend({

    init: function(view_manager, session, element_id, dataset, view_id) {
        this._super(session, element_id);
        this.view_manager = view_manager;
        this.dataset = dataset;
        this.model = this.dataset.model;
        this.view_id = view_id;
    },
    do_show: function () {
        // TODO: re-trigger search
        this.$element.show();
    },
    do_hide: function () {
        this.$element.hide();
    },
    start: function() {
        this.rpc("/base_graph/graphview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
    },
    on_loaded: function(data) {
        this.all_fields = data.all_fields;
        this.fields_view = data.fields_view;
        this.name = this.fields_view.name || this.fields_view.arch.attrs.string;
        this.view_id = this.fields_view.view_id;
        this.chart = this.fields_view.arch.attrs.type || 'pie';
        this.fields = this.fields_view.fields;
        this.chart_info_fields = [];
        this.operator_field = '';
        this.operator_field_one = '';
        this.operator = [];
        this.group_field = '';
        this.orientation = this.fields_view.arch.attrs.orientation || '';

        _.each(this.fields_view.arch.children, function (field) {
            if (field.attrs.operator) {
                this.operator.push(field.attrs.name);
            }
            else if (field.attrs.group) {
                this.group_field = field.attrs.name;
            }
            else {
                this.chart_info_fields.push(field.attrs.name);
            }
        }, this);

        this.operator_field = this.operator[0];
        if(this.operator.length > 1){
            this.operator_field_one = this.operator[1];
        }
        if(this.operator == ''){
            this.operator_field = this.chart_info_fields[1];
        }
        this.chart_info = this.chart_info_fields[0];
        this.x_title = this.fields[this.chart_info_fields[0]]['string'];
        this.y_title = this.fields[this.operator_field]['string'];
        this.load_chart();
    },

    load_chart: function(data) {
        var self = this;
        if(data){
            this.x_title = this.all_fields[this.chart_info_fields]['string'];
            this.y_title = this.all_fields[this.operator_field]['string'];
            self.schedule_chart(data);
        }else{
            this.dataset.read_ids(
                this.dataset.ids,
                {},
                function(res) {
                    self.schedule_chart(res);
                }
            );
        }
    },

    schedule_chart: function(results) {
        this.$element.html(QWeb.render("GraphView", {"fields_view": this.fields_view, "chart": this.chart,'view_id': this.view_id}));
        if (results.length){
            _.each(results, function (result) {
                _.each(result, function (field_value, field_name) {
                    if (typeof field_value == 'object') {
                        result[field_name] = field_value[field_value.length - 1];
                    }
                    if (typeof field_value == 'string'){
                        var choices = this.all_fields[field_name]['selection'];
                        _.each(choices, function (choice) {
                            if(field_value == choice[0]){
                               result[field_name] = choice;
                            }
                        });
                    }
                }, this);
            }, this);

            var opration_fld = {};
            _.each(results, function (result) {
                var gen_key = result[this.chart_info_fields]+"_"+result[this.group_field];
                var map_val = {};
                if (opration_fld[gen_key] == undefined){
                    map_val[this.operator_field] = result[this.operator_field];
                    if (this.operator.length > 1){
                        map_val[this.operator_field_one] = result[this.operator_field_one];
                    }
                    map_val[this.chart_info_fields] = result[this.chart_info_fields];
                    if (this.group_field){
                        map_val[this.group_field] = (typeof result[this.group_field] == 'object')?result[this.group_field][1]:result[this.group_field];
                    }
                } else {
                    map_val = opration_fld[gen_key];
                    map_val[this.operator_field] += result[this.operator_field];
                    if (this.operator.length > 1){
                        map_val[this.operator_field_one] += result[this.operator_field_one];
                    }
                }
                opration_fld[gen_key] = map_val;
            }, this);

            var graph_data = [];
            _.each(opration_fld, function (column_data) {
                graph_data.push(column_data);
            });

            if(this.chart == 'bar') {
                return this.schedule_bar(graph_data);
            } else if(this.chart == "pie") {
                return this.schedule_pie(graph_data);
            }
        }
    },

    schedule_bar: function(results) {
        var self = this;
        var view_chart = '';
        var xystr = {};
        var group_list = [];
        var newkey = '', newkey_one;

        if(self.group_field && (this.operator.length <= 1)){
            view_chart = self.orientation == 'horizontal'? 'stackedBarH' : 'stackedBar';
        }else{
            view_chart = self.orientation == 'horizontal'? 'barH' : 'bar';
        }

        _.each(results, function (result) {
            if (self.group_field && (this.operator.length <= 1)){
                newkey = result[self.group_field].split(' ').join('_');
            } else {
                newkey = "val";
            }

            if (jQuery.inArray(newkey, group_list) == -1){
                group_list.push(newkey);
                if(this.operator.length > 1){
                    newkey_one = "val1";
                    group_list.push(newkey_one);
                }
            }
        }, this);

        _.each(results, function (result) {
            var xystring = result[self.chart_info_fields],
                xyname = {};
            if (self.group_field && (self.operator.length <= 1)){
                newkey = result[self.group_field].split(' ').join('_');
            }else{
                newkey = "val";
            }
            if (xystr[xystring] == undefined){
                xyname[self.chart_info_fields] = xystring;
                _.each(group_list, function (group) {
                    xyname[group] = 0.0001;
                });
            } else {
                xyname = xystr[xystring];
            }
            xyname[newkey] = result[self.operator_field];
            if (self.operator.length > 1){
                xyname[newkey_one] = result[self.operator_field_one];
            }
            xystr[xystring] = xyname;
        });

        //for legend color
        var grp_color = _.map(group_list, function (group_legend, index) {
            var legend = {color: COLOR_PALETTE[index]};

            if (group_legend == "val"){
                legend['text'] = self.fields[self.operator_field]['string']
            }else if(group_legend == "val1"){
                legend['text'] = self.fields[self.operator_field_one]['string']
            }else{
                legend['text'] = group_legend;
            }
            return legend;
        });

        //for axis's value and title
        var temp_ax = {
            template: self.chart_info_fields,
            title: ""+self.x_title+""
        };
        var oth_ax = {
            lines: true,
            title: ""+self.y_title+""
        };

        var x_ax, y_ax;
        if (self.orientation == 'horizontal'){
             x_ax = oth_ax;
             y_ax = temp_ax;
        }else{
             x_ax = temp_ax;
             y_ax = oth_ax;
        }

        var bar_chart = new dhtmlxchartChart({
            view: view_chart,
            container: self.view_id+"-barchart",
            value:"#"+group_list[0]+"#",
            gradient: "3d",
            border: false,
            width: 1024,
            radius: 0,
            color:grp_color[0]['color'],
            origin:0,
            xAxis:{
                template:function(obj){
                    if(x_ax['template']){
                        var val = obj[x_ax['template']];
                        val = (typeof val == 'object')?val[1]:(!val?'Undefined':val);
                        return val;
                    }else{
                        return obj;
                    }
                },
                title:x_ax['title'],
                lines:x_ax['lines']
            },
            yAxis:{
                template:function(obj){
                    if(y_ax['template']){
                        var vals = obj[y_ax['template']];
                        vals = (typeof vals == 'object')?vals[1]:(!vals?'Undefined':vals);
                        return vals;
                    }else{
                        return obj;
                    }
                },
                title:y_ax['title'],
                lines: y_ax['lines']
            },
            padding: {
                left: 75
            },
            legend: {
                values: grp_color,
                align:"left",
                valign:"top",
                layout: "x",
                marker:{
                    type:"round",
                    width:12
                }
            }
        });
        for (var m = 1; m<group_list.length;m++){
            bar_chart.addSeries({
                value: "#"+group_list[m]+"#",
                color: grp_color[m]['color']
            });
        }
        bar_chart.parse(_.values(xystr), "json");
        bar_chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(bar_chart.get(id));
        });
    },
    schedule_pie: function(result) {
        var self = this;
        var chart =  new dhtmlxchartChart({
            view:"pie3D",
            container:self.view_id+"-piechart",
            value:"#"+self.operator_field+"#",
            pieInnerText:function(obj) {
                var sum = chart.sum("#"+self.operator_field+"#");
                var val = obj[self.operator_field] / sum * 100 ;
                return Math.round(val * 10)/10 + "%";
            },
            gradient:"3d",
            height: 20,
            radius: 200,
            legend: {
                width: 300,
                align:"left",
                valign:"top",
                layout: "x",
                marker:{
                    type:"round",
                    width:12
                },
                template:function(obj){
                    var val = obj[self.chart_info_fields];
                    val = (typeof val == 'object')?val[1]:val;
                    return val;
                }
            }
        });
        chart.parse(result,"json");
        chart.attachEvent("onItemClick", function(id) {
            self.open_list_view(chart.get(id));
        });
    },
    open_list_view : function (id){
        var self = this;
        id = id[self.chart_info_fields];
        if (typeof id == 'object'){
            id = id[0];
        }
        if(this.view_manager.action.context){
           this.view_manager.action.context = {};
        }
        if(!this.view_manager.action.domain) {
            this.view_manager.action.domain = [[self.chart_info_fields, '=', id],['id','in',self.dataset.ids]];
        } else {
            this.view_manager.action.domain.push([self.chart_info_fields, '=', id],['id','in',self.dataset.ids]);
        }
        var action_manager = new openerp.base.ActionManager(this.view_manager.session, this.view_manager.element_id);
        action_manager.start();
        action_manager.do_action(this.view_manager.action);
    },
    do_search: function(domains, contexts, groupbys) {
        var self = this;
        this.rpc('/base/session/eval_domain_and_context', {
            domains: domains,
            contexts: contexts,
            group_by_seq: groupbys
        }, function (results) {
            // TODO: handle non-empty results.group_by with read_group
            if(results.group_by  && results.group_by != ''){
                self.chart_info_fields = results.group_by[0];
            }else{
                self.chart_info_fields = self.chart_info;
            }
            self.dataset.context = self.context = results.context;
            self.dataset.domain = self.domain = results.domain;
            self.dataset.read_slice({}, 0, self.limit,function(response){
                self.load_chart(response);
            });
        });
    }

});

// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them

};

// vim:et fdc=0 fdl=0:
<|endoftext|>