<commit_before>const errors = require('@tryghost/errors');
const nql = require('@nexes/nql');

module.exports = class EventRepository {
    constructor({
        EmailRecipient,
        MemberSubscribeEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberLoginEvent,
        MemberPaidSubscriptionEvent,
        labsService
    }) {
        this._MemberSubscribeEvent = MemberSubscribeEvent;
        this._MemberPaidSubscriptionEvent = MemberPaidSubscriptionEvent;
        this._MemberPaymentEvent = MemberPaymentEvent;
        this._MemberStatusEvent = MemberStatusEvent;
        this._MemberLoginEvent = MemberLoginEvent;
        this._EmailRecipient = EmailRecipient;
        this._labsService = labsService;
    }

    async registerPayment(data) {
        await this._MemberPaymentEvent.add({
            ...data,
            source: 'stripe'
        });
    }

    async getNewsletterSubscriptionEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberSubscribeEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'newsletter_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getSubscriptionEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberPaidSubscriptionEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'subscription_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getPaymentEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberPaymentEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'payment_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getLoginEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberLoginEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'login_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getSignupEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: ['from_status:null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberStatusEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'signup_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getEmailDeliveredEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member', 'email'],
            filter: ['delivered_at:-null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'delivered_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');
        options.order = options.order.replace(/created_at/g, 'delivered_at');

        const {data: models, meta} = await this._EmailRecipient.findPage(
            options
        );

        const data = models.map((model) => {
            return {
                type: 'email_delivered_event',
                data: {
                    member_id: model.get('member_id'),
                    created_at: model.get('delivered_at'),
                    member: model.related('member').toJSON(),
                    email: model.related('email').toJSON()
                }
            };
        });

        return {
            data,
            meta
        };
    }

    async getEmailOpenedEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member', 'email'],
            filter: ['opened_at:-null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'opened_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');
        options.order = options.order.replace(/created_at/g, 'opened_at');

        const {data: models, meta} = await this._EmailRecipient.findPage(
            options
        );

        const data = models.map((model) => {
            return {
                type: 'email_opened_event',
                data: {
                    member_id: model.get('member_id'),
                    created_at: model.get('opened_at'),
                    member: model.related('member').toJSON(),
                    email: model.related('email').toJSON()
                }
            };
        });

        return {
            data,
            meta
        };
    }

    async getEmailFailedEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member', 'email'],
            filter: ['failed_at:-null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'failed_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');
        options.order = options.order.replace(/created_at/g, 'failed_at');

        const {data: models, meta} = await this._EmailRecipient.findPage(
            options
        );

        const data = models.map((model) => {
            return {
                type: 'email_failed_event',
                data: {
                    member_id: model.get('member_id'),
                    created_at: model.get('failed_at'),
                    member: model.related('member').toJSON(),
                    email: model.related('email').toJSON()
                }
            };
        });

        return {
            data,
            meta
        };
    }

    /**
     * Extract a subset of NQL.
     * There are only a few properties allowed.
     * Parenthesis are forbidden.
     * Only ANDs are supported when combining properties.
     */
    getNQLSubset(filter) {
        if (!filter) {
            return {};
        }

        const lex = nql(filter).lex();

        const allowedFilters = ['type','data.created_at','data.member_id'];
        const properties = lex
            .filter(x => x.token === 'PROP')
            .map(x => x.matched.slice(0, -1));
        if (properties.some(prop => !allowedFilters.includes(prop))) {
            throw new errors.IncorrectUsageError({
                message: 'The only allowed filters are `type`, `data.created_at` and `data.member_id`'
            });
        }

        if (lex.find(x => x.token === 'LPAREN')) {
            throw new errors.IncorrectUsageError({
                message: 'The filter can\'t contain parenthesis.'
            });
        }

        const jsonFilter = nql(filter).toJSON();
        const keys = Object.keys(jsonFilter);

        if (keys.length === 1 && keys[0] === '$or') {
            throw new errors.IncorrectUsageError({
                message: 'The top level-filters can only combined with ANDs (+) and not ORs (,).'
            });
        }

        // The filter is validated, it only contains one level of filters concatenated with `+`
        const filters = filter.split('+');

        /** @type {Object.<string, string>} */
        let result = {};

        for (const f of filters) {
            // dirty way to parse a property, but it works according to https://github.com/NexesJS/NQL-Lang/blob/0e12d799a3a9c4d8651444e9284ce16c19cbc4f0/src/nql.l#L18
            const key = f.split(':')[0];
            if (!result[key]) {
                result[key] = f;
            } else {
                result[key] += '+' + f;
            }
        }

        return result;
    }

    async getEventTimeline(options = {}) {
        if (!options.limit) {
            options.limit = 10;
        }

        // Changing this order might need a change in the query functions
        // because of the different underlying models.
        options.order = 'created_at desc';

        // Create a list of all events that can be queried
        const pageActions = [
            {type: 'newsletter_event', action: 'getNewsletterSubscriptionEvents'},
            {type: 'subscription_event', action: 'getSubscriptionEvents'},
            {type: 'login_event', action: 'getLoginEvents'},
            {type: 'payment_event', action: 'getPaymentEvents'},
            {type: 'signup_event', action: 'getSignupEvents'}
        ];
        if (this._labsService.isSet('membersActivityFeed') && this._EmailRecipient) {
            pageActions.push({type: 'email_delivered_event', action: 'getEmailDelieveredEvents'});
            pageActions.push({type: 'email_opened_event', action: 'getEmailOpenedEvents'});
            pageActions.push({type: 'email_failed_event', action: 'getEmailFailedEvents'});
        }

        let filters = this.getNQLSubset(options.filter);

        //Filter events to query
        const filteredPages = filters.type ? pageActions.filter(page => nql(filters.type).queryJSON(page)) : pageActions;

        //Start the promises
        const pages = filteredPages.map(page => this[page.action](options, filters));

        const allEventPages = await Promise.all(pages);

        const allEvents = allEventPages.reduce((accumulator, page) => accumulator.concat(page.data), []);

        return allEvents.sort((a, b) => {
            return new Date(b.data.created_at) - new Date(a.data.created_at);
        }).reduce((memo, event, i) => {
            if (this._labsService.isSet('membersActivityFeed')) {
                //disable the event filtering
                return memo.concat(event);
            }
            if (event.type === 'newsletter_event' && event.data.subscribed) {
                const previousEvent = allEvents[i - 1];
                const nextEvent = allEvents[i + 1];
                const currentMember = event.data.member_id;

                if (previousEvent && previousEvent.type === 'signup_event') {
                    const previousMember = previousEvent.data.member_id;

                    if (currentMember === previousMember) {
                        return memo;
                    }
                }

                if (nextEvent && nextEvent.type === 'signup_event') {
                    const nextMember = nextEvent.data.member_id;

                    if (currentMember === nextMember) {
                        return memo;
                    }
                }
            }
            return memo.concat(event);
        }, []).slice(0, options.limit);
    }

    async getSubscriptions() {
        const results = await this._MemberSubscribeEvent.findAll({
            aggregateSubscriptionDeltas: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result, index) => {
            if (index === 0) {
                return [{
                    date: result.date,
                    subscribed: result.subscribed_delta
                }];
            }
            return accumulator.concat([{
                date: result.date,
                subscribed: result.subscribed_delta + accumulator[index - 1].subscribed
            }]);
        }, []);

        return cumulativeResults;
    }

    async getMRR() {
        const results = await this._MemberPaidSubscriptionEvent.findAll({
            aggregateMRRDeltas: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result) => {
            if (!accumulator[result.currency]) {
                return {
                    ...accumulator,
                    [result.currency]: [{
                        date: result.date,
                        mrr: result.mrr_delta,
                        currency: result.currency
                    }]
                };
            }
            return {
                ...accumulator,
                [result.currency]: accumulator[result.currency].concat([{
                    date: result.date,
                    mrr: result.mrr_delta + accumulator[result.currency].slice(-1)[0].mrr,
                    currency: result.currency
                }])
            };
        }, {});

        return cumulativeResults;
    }

    async getVolume() {
        const results = await this._MemberPaymentEvent.findAll({
            aggregatePaymentVolume: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result) => {
            if (!accumulator[result.currency]) {
                return {
                    ...accumulator,
                    [result.currency]: [{
                        date: result.date,
                        volume: result.volume_delta,
                        currency: result.currency
                    }]
                };
            }
            return {
                ...accumulator,
                [result.currency]: accumulator[result.currency].concat([{
                    date: result.date,
                    volume: result.volume_delta + accumulator[result.currency].slice(-1)[0].volume,
                    currency: result.currency
                }])
            };
        }, {});

        return cumulativeResults;
    }

    async getStatuses() {
        const results = await this._MemberStatusEvent.findAll({
            aggregateStatusCounts: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result, index) => {
            if (index === 0) {
                return [{
                    date: result.date,
                    paid: result.paid_delta,
                    comped: result.comped_delta,
                    free: result.free_delta
                }];
            }
            return accumulator.concat([{
                date: result.date,
                paid: result.paid_delta + accumulator[index - 1].paid,
                comped: result.comped_delta + accumulator[index - 1].comped,
                free: result.free_delta + accumulator[index - 1].free
            }]);
        }, []);

        return cumulativeResults;
    }
};
<commit_msg>🐛 Fixed typo in `getEventTimeline`<commit_after>const errors = require('@tryghost/errors');
const nql = require('@nexes/nql');

module.exports = class EventRepository {
    constructor({
        EmailRecipient,
        MemberSubscribeEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberLoginEvent,
        MemberPaidSubscriptionEvent,
        labsService
    }) {
        this._MemberSubscribeEvent = MemberSubscribeEvent;
        this._MemberPaidSubscriptionEvent = MemberPaidSubscriptionEvent;
        this._MemberPaymentEvent = MemberPaymentEvent;
        this._MemberStatusEvent = MemberStatusEvent;
        this._MemberLoginEvent = MemberLoginEvent;
        this._EmailRecipient = EmailRecipient;
        this._labsService = labsService;
    }

    async registerPayment(data) {
        await this._MemberPaymentEvent.add({
            ...data,
            source: 'stripe'
        });
    }

    async getNewsletterSubscriptionEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberSubscribeEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'newsletter_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getSubscriptionEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberPaidSubscriptionEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'subscription_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getPaymentEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberPaymentEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'payment_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getLoginEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: []
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberLoginEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'login_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getSignupEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member'],
            filter: ['from_status:null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'created_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');

        const {data: models, meta} = await this._MemberStatusEvent.findPage(options);

        const data = models.map((model) => {
            return {
                type: 'signup_event',
                data: model.toJSON(options)
            };
        });

        return {
            data,
            meta
        };
    }

    async getEmailDeliveredEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member', 'email'],
            filter: ['delivered_at:-null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'delivered_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');
        options.order = options.order.replace(/created_at/g, 'delivered_at');

        const {data: models, meta} = await this._EmailRecipient.findPage(
            options
        );

        const data = models.map((model) => {
            return {
                type: 'email_delivered_event',
                data: {
                    member_id: model.get('member_id'),
                    created_at: model.get('delivered_at'),
                    member: model.related('member').toJSON(),
                    email: model.related('email').toJSON()
                }
            };
        });

        return {
            data,
            meta
        };
    }

    async getEmailOpenedEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member', 'email'],
            filter: ['opened_at:-null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'opened_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');
        options.order = options.order.replace(/created_at/g, 'opened_at');

        const {data: models, meta} = await this._EmailRecipient.findPage(
            options
        );

        const data = models.map((model) => {
            return {
                type: 'email_opened_event',
                data: {
                    member_id: model.get('member_id'),
                    created_at: model.get('opened_at'),
                    member: model.related('member').toJSON(),
                    email: model.related('email').toJSON()
                }
            };
        });

        return {
            data,
            meta
        };
    }

    async getEmailFailedEvents(options = {}, filters = {}) {
        options = {
            ...options,
            withRelated: ['member', 'email'],
            filter: ['failed_at:-null']
        };
        if (filters['data.created_at']) {
            options.filter.push(filters['data.created_at'].replace(/data.created_at:/g, 'failed_at:'));
        }
        if (filters['data.member_id']) {
            options.filter.push(filters['data.member_id'].replace(/data.member_id:/g, 'member_id:'));
        }
        options.filter = options.filter.join('+');
        options.order = options.order.replace(/created_at/g, 'failed_at');

        const {data: models, meta} = await this._EmailRecipient.findPage(
            options
        );

        const data = models.map((model) => {
            return {
                type: 'email_failed_event',
                data: {
                    member_id: model.get('member_id'),
                    created_at: model.get('failed_at'),
                    member: model.related('member').toJSON(),
                    email: model.related('email').toJSON()
                }
            };
        });

        return {
            data,
            meta
        };
    }

    /**
     * Extract a subset of NQL.
     * There are only a few properties allowed.
     * Parenthesis are forbidden.
     * Only ANDs are supported when combining properties.
     */
    getNQLSubset(filter) {
        if (!filter) {
            return {};
        }

        const lex = nql(filter).lex();

        const allowedFilters = ['type','data.created_at','data.member_id'];
        const properties = lex
            .filter(x => x.token === 'PROP')
            .map(x => x.matched.slice(0, -1));
        if (properties.some(prop => !allowedFilters.includes(prop))) {
            throw new errors.IncorrectUsageError({
                message: 'The only allowed filters are `type`, `data.created_at` and `data.member_id`'
            });
        }

        if (lex.find(x => x.token === 'LPAREN')) {
            throw new errors.IncorrectUsageError({
                message: 'The filter can\'t contain parenthesis.'
            });
        }

        const jsonFilter = nql(filter).toJSON();
        const keys = Object.keys(jsonFilter);

        if (keys.length === 1 && keys[0] === '$or') {
            throw new errors.IncorrectUsageError({
                message: 'The top level-filters can only combined with ANDs (+) and not ORs (,).'
            });
        }

        // The filter is validated, it only contains one level of filters concatenated with `+`
        const filters = filter.split('+');

        /** @type {Object.<string, string>} */
        let result = {};

        for (const f of filters) {
            // dirty way to parse a property, but it works according to https://github.com/NexesJS/NQL-Lang/blob/0e12d799a3a9c4d8651444e9284ce16c19cbc4f0/src/nql.l#L18
            const key = f.split(':')[0];
            if (!result[key]) {
                result[key] = f;
            } else {
                result[key] += '+' + f;
            }
        }

        return result;
    }

    async getEventTimeline(options = {}) {
        if (!options.limit) {
            options.limit = 10;
        }

        // Changing this order might need a change in the query functions
        // because of the different underlying models.
        options.order = 'created_at desc';

        // Create a list of all events that can be queried
        const pageActions = [
            {type: 'newsletter_event', action: 'getNewsletterSubscriptionEvents'},
            {type: 'subscription_event', action: 'getSubscriptionEvents'},
            {type: 'login_event', action: 'getLoginEvents'},
            {type: 'payment_event', action: 'getPaymentEvents'},
            {type: 'signup_event', action: 'getSignupEvents'}
        ];
        if (this._labsService.isSet('membersActivityFeed') && this._EmailRecipient) {
            pageActions.push({type: 'email_delivered_event', action: 'getEmailDeliveredEvents'});
            pageActions.push({type: 'email_opened_event', action: 'getEmailOpenedEvents'});
            pageActions.push({type: 'email_failed_event', action: 'getEmailFailedEvents'});
        }

        let filters = this.getNQLSubset(options.filter);

        //Filter events to query
        const filteredPages = filters.type ? pageActions.filter(page => nql(filters.type).queryJSON(page)) : pageActions;

        //Start the promises
        const pages = filteredPages.map(page => this[page.action](options, filters));

        const allEventPages = await Promise.all(pages);

        const allEvents = allEventPages.reduce((accumulator, page) => accumulator.concat(page.data), []);

        return allEvents.sort((a, b) => {
            return new Date(b.data.created_at) - new Date(a.data.created_at);
        }).reduce((memo, event, i) => {
            if (this._labsService.isSet('membersActivityFeed')) {
                //disable the event filtering
                return memo.concat(event);
            }
            if (event.type === 'newsletter_event' && event.data.subscribed) {
                const previousEvent = allEvents[i - 1];
                const nextEvent = allEvents[i + 1];
                const currentMember = event.data.member_id;

                if (previousEvent && previousEvent.type === 'signup_event') {
                    const previousMember = previousEvent.data.member_id;

                    if (currentMember === previousMember) {
                        return memo;
                    }
                }

                if (nextEvent && nextEvent.type === 'signup_event') {
                    const nextMember = nextEvent.data.member_id;

                    if (currentMember === nextMember) {
                        return memo;
                    }
                }
            }
            return memo.concat(event);
        }, []).slice(0, options.limit);
    }

    async getSubscriptions() {
        const results = await this._MemberSubscribeEvent.findAll({
            aggregateSubscriptionDeltas: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result, index) => {
            if (index === 0) {
                return [{
                    date: result.date,
                    subscribed: result.subscribed_delta
                }];
            }
            return accumulator.concat([{
                date: result.date,
                subscribed: result.subscribed_delta + accumulator[index - 1].subscribed
            }]);
        }, []);

        return cumulativeResults;
    }

    async getMRR() {
        const results = await this._MemberPaidSubscriptionEvent.findAll({
            aggregateMRRDeltas: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result) => {
            if (!accumulator[result.currency]) {
                return {
                    ...accumulator,
                    [result.currency]: [{
                        date: result.date,
                        mrr: result.mrr_delta,
                        currency: result.currency
                    }]
                };
            }
            return {
                ...accumulator,
                [result.currency]: accumulator[result.currency].concat([{
                    date: result.date,
                    mrr: result.mrr_delta + accumulator[result.currency].slice(-1)[0].mrr,
                    currency: result.currency
                }])
            };
        }, {});

        return cumulativeResults;
    }

    async getVolume() {
        const results = await this._MemberPaymentEvent.findAll({
            aggregatePaymentVolume: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result) => {
            if (!accumulator[result.currency]) {
                return {
                    ...accumulator,
                    [result.currency]: [{
                        date: result.date,
                        volume: result.volume_delta,
                        currency: result.currency
                    }]
                };
            }
            return {
                ...accumulator,
                [result.currency]: accumulator[result.currency].concat([{
                    date: result.date,
                    volume: result.volume_delta + accumulator[result.currency].slice(-1)[0].volume,
                    currency: result.currency
                }])
            };
        }, {});

        return cumulativeResults;
    }

    async getStatuses() {
        const results = await this._MemberStatusEvent.findAll({
            aggregateStatusCounts: true
        });

        const resultsJSON = results.toJSON();

        const cumulativeResults = resultsJSON.reduce((accumulator, result, index) => {
            if (index === 0) {
                return [{
                    date: result.date,
                    paid: result.paid_delta,
                    comped: result.comped_delta,
                    free: result.free_delta
                }];
            }
            return accumulator.concat([{
                date: result.date,
                paid: result.paid_delta + accumulator[index - 1].paid,
                comped: result.comped_delta + accumulator[index - 1].comped,
                free: result.free_delta + accumulator[index - 1].free
            }]);
        }, []);

        return cumulativeResults;
    }
};
<|endoftext|>


<commit_before>/* global Blob window URL */
import CompositeClosureHelper from 'paraviewweb/src/Common/Core/CompositeClosureHelper';

function wslinkImageStream(publicAPI, model) {
  if (!model.client) throw Error('Client must be provided');

  model.metadata = null;
  model.activeURL = null;
  model.fps = 0;
  model.lastTime = +new Date();

  model.lastImageReadyEvent = null;

  // This functionality is apparently unused.
  publicAPI.enableView = (enabled) => {
    model.client.VtkImageDelivery.enableView(model.view_id, enabled);
  };

  publicAPI.startInteractiveQuality = () =>
    model.client.VtkImageDelivery.viewQuality(
      model.view_id,
      model.interactiveQuality,
      model.interactiveRatio
    );

  publicAPI.stopInteractiveQuality = () =>
    model.client.VtkImageDelivery.viewQuality(
      model.view_id,
      model.stillQuality,
      model.stillRatio
    );

  publicAPI.setMaxFrameRate = (maxFPS) =>
    model.client.VtkImageDelivery.setMaxFrameRate(maxFPS);

  publicAPI.setViewSize = (width, height) =>
    model.client.VtkImageDelivery.viewSize(model.view_id, width, height);

  publicAPI.invalidateCache = () =>
    model.client.VtkImageDelivery.invalidateCache(model.view_id);

  publicAPI.updateQuality = (stillQuality = 100, interactiveQuality = 50) => {
    model.stillQuality = stillQuality;
    model.interactiveQuality = interactiveQuality;
  };

  publicAPI.updateResolutionRatio = (sRatio = 1, iRatio = 0.5) => {
    model.stillRatio = sRatio;
    model.interactiveRatio = iRatio;
  };

  publicAPI.unsubscribeRenderTopic = () => {
    model.client.VtkImageDelivery.offRenderChange(
      model.renderTopicSubscription
    ).then(
      (unsubSuccess) => {
        console.log('Unsubscribe resolved ', unsubSuccess);
      },
      (unsubFailure) => {
        console.log('Unsubscribe error ', unsubFailure);
      }
    );
  };

  // subscribeRenderTopic = () => {
  //   model.client.VtkImageDelivery.onRenderChange(publicAPI.viewChanged).then((subscription) => {
  //     model.renderTopicSubscription = subscription;
  //   }, (subError) => {
  //     console.log('Failed to subscribe to topic');
  //     console.log(subError);
  //   });
  // }

  publicAPI.removeRenderObserver = (viewId) => {
    model.client.VtkImageDelivery.removeRenderObserver(viewId).then(
      (successResult) => {
        console.log(`Removed observer from view ${viewId} succeeded`);
        console.log(successResult);
      },
      (failureResult) => {
        console.log(`Failed to remove observer from view ${viewId}`);
        console.log(failureResult);
      }
    );
  };

  // addRenderObserver(viewId) {
  //   model.client.VtkImageDelivery.addRenderObserver(viewId).then((successResult) => {
  //     console.log(`Successfully added observer to view ${viewId}`);
  //     console.log(successResult);
  //   }, (failureResult) => {
  //     console.log(`Failed to add observer to view ${viewId}`);
  //     console.log(failureResult);
  //   });
  // }

  publicAPI.setViewId = (viewId) => {
    if (model.view_id === viewId) {
      return false;
    }
    model.client.VtkImageDelivery.removeRenderObserver(model.view_id);
    model.view_id = viewId;
    model.client.VtkImageDelivery.addRenderObserver(model.view_id);
    return true;
  };

  publicAPI.viewChanged = (data) => {
    const msg = data[0];
    if (!msg || !msg.image) return;
    const imgBlob = new Blob([msg.image], {
      type: model.mimeType,
    });
    if (model.activeURL) {
      window.URL.revokeObjectURL(model.activeURL);
      model.activeURL = null;
    }
    model.activeURL = URL.createObjectURL(imgBlob);
    const time = +new Date();
    model.fps = Math.floor(10000 / (time - model.lastTime)) / 10;
    model.lastTime = time;

    model.lastImageReadyEvent = {
      url: model.activeURL,
      fps: model.fps,
      metadata: {
        size: msg.size,
        id: msg.id,
        memory: msg.memsize,
        workTime: msg.workTime,
      },
    };

    publicAPI.fireImageReady(model.lastImageReadyEvent);
  };

  /* eslint-disable camelcase */
  publicAPI.connect = ({ view_id = -1, size = [500, 500] }) =>
    // Subscribe to pubsub topic and add view observer
    new Promise((resolve, reject) => {
      model.view_id = view_id;
      model.width = size[0];
      model.height = size[1];

      model.client.VtkImageDelivery.onRenderChange(publicAPI.viewChanged).then(
        (subscription) => {
          model.renderTopicSubscription = subscription;
          model.client.VtkImageDelivery.addRenderObserver(view_id).then(
            (successResult) => {
              if (successResult.viewId) {
                model.view_id = successResult.viewId;
              }
              console.log(`Successfully added observer to view ${view_id}`);
              console.log(successResult);
              resolve(successResult);
            },
            (failureResult) => {
              console.log(`Failed to add observer to view ${view_id}`);
              console.log(failureResult);
              reject(failureResult);
            }
          );
        },
        (subError) => {
          console.log('Failed to subscribe to topic');
          console.log(subError);
          reject(subError);
        }
      );
    });
  /* eslint-enable camelcase */

  function cleanUp() {
    publicAPI.removeRenderObserver(model.view_id);
    publicAPI.unsubscribeRenderTopic();
  }

  publicAPI.destroy = CompositeClosureHelper.chain(cleanUp, publicAPI.destroy);

  publicAPI.getLastImageReadyEvent = () => model.lastImageReadyEvent;
}

const DEFAULT_VALUES = {
  stillRatio: 1,
  interactiveRatio: 0.5,
  stillQuality: 100,
  interactiveQuality: 50,
  mimeType: 'image/jpeg',
  view_id: -1,
};

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  CompositeClosureHelper.destroy(publicAPI, model);
  CompositeClosureHelper.event(publicAPI, model, 'ImageReady');
  CompositeClosureHelper.isA(publicAPI, model, 'WslinkImageStream');

  wslinkImageStream(publicAPI, model);
}

// ----------------------------------------------------------------------------
export const newInstance = CompositeClosureHelper.newInstance(extend);

export default { newInstance, extend };
<commit_msg>fix(WslinkImageStream): Do not revoke url too soon<commit_after>/* global Blob window URL */
import CompositeClosureHelper from 'paraviewweb/src/Common/Core/CompositeClosureHelper';

function wslinkImageStream(publicAPI, model) {
  if (!model.client) throw Error('Client must be provided');

  model.metadata = null;
  model.activeURL = null;
  model.fps = 0;
  model.lastTime = Date.now();
  model.urlToRevoke = [];

  model.lastImageReadyEvent = null;

  // This functionality is apparently unused.
  publicAPI.enableView = (enabled) => {
    model.client.VtkImageDelivery.enableView(model.view_id, enabled);
  };

  publicAPI.startInteractiveQuality = () =>
    model.client.VtkImageDelivery.viewQuality(
      model.view_id,
      model.interactiveQuality,
      model.interactiveRatio
    );

  publicAPI.stopInteractiveQuality = () =>
    model.client.VtkImageDelivery.viewQuality(
      model.view_id,
      model.stillQuality,
      model.stillRatio
    );

  publicAPI.setMaxFrameRate = (maxFPS) =>
    model.client.VtkImageDelivery.setMaxFrameRate(maxFPS);

  publicAPI.setViewSize = (width, height) =>
    model.client.VtkImageDelivery.viewSize(model.view_id, width, height);

  publicAPI.invalidateCache = () =>
    model.client.VtkImageDelivery.invalidateCache(model.view_id);

  publicAPI.updateQuality = (stillQuality = 100, interactiveQuality = 50) => {
    model.stillQuality = stillQuality;
    model.interactiveQuality = interactiveQuality;
  };

  publicAPI.updateResolutionRatio = (sRatio = 1, iRatio = 0.5) => {
    model.stillRatio = sRatio;
    model.interactiveRatio = iRatio;
  };

  publicAPI.unsubscribeRenderTopic = () => {
    model.client.VtkImageDelivery.offRenderChange(
      model.renderTopicSubscription
    ).then(
      (unsubSuccess) => {
        console.log('Unsubscribe resolved ', unsubSuccess);
      },
      (unsubFailure) => {
        console.log('Unsubscribe error ', unsubFailure);
      }
    );
  };

  // subscribeRenderTopic = () => {
  //   model.client.VtkImageDelivery.onRenderChange(publicAPI.viewChanged).then((subscription) => {
  //     model.renderTopicSubscription = subscription;
  //   }, (subError) => {
  //     console.log('Failed to subscribe to topic');
  //     console.log(subError);
  //   });
  // }

  publicAPI.removeRenderObserver = (viewId) => {
    model.client.VtkImageDelivery.removeRenderObserver(viewId).then(
      (successResult) => {
        console.log(`Removed observer from view ${viewId} succeeded`);
        console.log(successResult);
      },
      (failureResult) => {
        console.log(`Failed to remove observer from view ${viewId}`);
        console.log(failureResult);
      }
    );
  };

  // addRenderObserver(viewId) {
  //   model.client.VtkImageDelivery.addRenderObserver(viewId).then((successResult) => {
  //     console.log(`Successfully added observer to view ${viewId}`);
  //     console.log(successResult);
  //   }, (failureResult) => {
  //     console.log(`Failed to add observer to view ${viewId}`);
  //     console.log(failureResult);
  //   });
  // }

  publicAPI.setViewId = (viewId) => {
    if (model.view_id === viewId) {
      return false;
    }
    model.client.VtkImageDelivery.removeRenderObserver(model.view_id);
    model.view_id = viewId;
    model.client.VtkImageDelivery.addRenderObserver(model.view_id);
    return true;
  };

  publicAPI.viewChanged = (data) => {
    const msg = data[0];
    if (!msg || !msg.image) return;
    const imgBlob = new Blob([msg.image], {
      type: model.mimeType,
    });
    if (model.activeURL) {
      model.urlToRevoke.push(model.activeURL);
      model.activeURL = null;
      while (model.urlToRevoke.length > 60) {
        const url = model.urlToRevoke.shift();
        window.URL.revokeObjectURL(url);
      }
    }
    model.activeURL = URL.createObjectURL(imgBlob);
    const time = Date.now();
    model.fps = Math.floor(10000 / (time - model.lastTime)) / 10;
    model.lastTime = time;

    model.lastImageReadyEvent = {
      url: model.activeURL,
      fps: model.fps,
      metadata: {
        size: msg.size,
        id: msg.id,
        memory: msg.memsize,
        workTime: msg.workTime,
      },
    };

    publicAPI.fireImageReady(model.lastImageReadyEvent);
  };

  /* eslint-disable camelcase */
  publicAPI.connect = ({ view_id = -1, size = [500, 500] }) =>
    // Subscribe to pubsub topic and add view observer
    new Promise((resolve, reject) => {
      model.view_id = view_id;
      model.width = size[0];
      model.height = size[1];

      model.client.VtkImageDelivery.onRenderChange(publicAPI.viewChanged).then(
        (subscription) => {
          model.renderTopicSubscription = subscription;
          model.client.VtkImageDelivery.addRenderObserver(view_id).then(
            (successResult) => {
              if (successResult.viewId) {
                model.view_id = successResult.viewId;
              }
              console.log(`Successfully added observer to view ${view_id}`);
              console.log(successResult);
              resolve(successResult);
            },
            (failureResult) => {
              console.log(`Failed to add observer to view ${view_id}`);
              console.log(failureResult);
              reject(failureResult);
            }
          );
        },
        (subError) => {
          console.log('Failed to subscribe to topic');
          console.log(subError);
          reject(subError);
        }
      );
    });
  /* eslint-enable camelcase */

  function cleanUp() {
    publicAPI.removeRenderObserver(model.view_id);
    publicAPI.unsubscribeRenderTopic();
    while (model.urlToRevoke.length) {
      window.URL.revokeObjectURL(model.urlToRevoke.pop());
    }
  }

  publicAPI.destroy = CompositeClosureHelper.chain(cleanUp, publicAPI.destroy);

  publicAPI.getLastImageReadyEvent = () => model.lastImageReadyEvent;
}

const DEFAULT_VALUES = {
  stillRatio: 1,
  interactiveRatio: 0.5,
  stillQuality: 100,
  interactiveQuality: 50,
  mimeType: 'image/jpeg',
  view_id: -1,
};

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  CompositeClosureHelper.destroy(publicAPI, model);
  CompositeClosureHelper.event(publicAPI, model, 'ImageReady');
  CompositeClosureHelper.isA(publicAPI, model, 'WslinkImageStream');

  wslinkImageStream(publicAPI, model);
}

// ----------------------------------------------------------------------------
export const newInstance = CompositeClosureHelper.newInstance(extend);

export default { newInstance, extend };
<|endoftext|>


<commit_before>// go.components.plumbing.testHelpers
// ==================================

(function(exports) {
  var oneElExists = function(selector) {
    return $(selector).get().length === 1;
  };

  var noElExists = function(selector) {
    return $(selector).get().length === 0;
  };

  _.extend(exports, {
    oneElExists: oneElExists,
    noElExists: noElExists
  });
})(go.testHelpers = {});
<commit_msg>Fix bad module title for go.testHelpers<commit_after>// go.testHelpers
// ==============

(function(exports) {
  var oneElExists = function(selector) {
    return $(selector).get().length === 1;
  };

  var noElExists = function(selector) {
    return $(selector).get().length === 0;
  };

  _.extend(exports, {
    oneElExists: oneElExists,
    noElExists: noElExists
  });
})(go.testHelpers = {});
<|endoftext|>


<commit_before>var request = require('request-json');
var config = require('./config');
var manifest = require('./manifest');
var server = require('./server');
var sendEmail = require('./sendEmail');

///////////////////////////////////////////////////////////////////////////////////////

// OTP STORE
// We assign a fresh OTP when the voter asks for an OTP for the first time.
// Then the OTP is returned (send via e-mail) whenever the voter queries
// for it.

var otp_store = {};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE otp
//

exports.otp = function otp(req, res)
{
    var email = req.body.email;
    if (email) {
        if (!server.eligibleVoters[email]) // Check if the voter is eligible
        {
            console.log('Voter not eligible', email);
            res.send({ ok: false, descr: 'Voter not eligible' });
        }
        else // eligible voter create a fresh OTP and send it
        {
            // Get a fresh otp (Java async call)
            console.log('Obtaining OTP...');
            server.getFreshOTP(function(err, otp) {
                if (err) {
                    console.log(' ...Internal error', err);
                    res.send({ ok: false, descr: 'Internal error. Cannot obtain an OTP' });
                }
                // We have a fresh otp now
                console.log(' ...Obtained a fresh OTP: ', otp);
                otp_store[email] = otp // store the opt under the voter id (email)
                /*
                console.log('Sending an emal with otp to', email, otp);
                sendEmail(email, 'Your One Time Password for sElect', otp, function (err,info) {
                    if (err) {
                        console.log(' ...Error:', err);
                    }else{
                        console.log(' ...E-mail sent: ' + info.response);
                    }
                    res.send({ ok: true });
                })
                */
                res.send({ ok: true }); // TODO: this is nestead of tha above
            });
        }
    }
    else
        res.send({ ok: false });
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE cast
//

exports.cast = function cast(req, res)
{
    var email = req.body.email;
    var otp = req.body.otp;
    var ballot = req.body.ballot;
    console.log('BALLOT COMING:', email, otp, ballot);

    // make sure that we have all the pieces:
    if (!email || !otp || !ballot ) {
        res.send({ ok: false });
        return;
    }

    // Check the otp (and, implicitly, the identifier)
    console.log('Checking the otp for a voter:', email, otp);
    if (otp_store[email] === otp) {
        console.log(' ...otp correct');

        // Cast the ballot:
        console.log('CollectBallot for', email );
        server.collectBallot(email, ballot, function(err, response) {
            if (err) {
                console.log(' ...Internal error: ', err);
                res.send({ ok: false, descr: 'Internal error' });
            }
            else if (response.data=='') {
                console.log(' ...Ballot rejected: ' );
                res.send({ ok: false, descr: 'Ballot rejected' });
                return;
            }
            else { // everything ok
                // TODO Now the result has a different form
                console.log(' ...Balloc accepted. Response = ', response);
                res.send({ ok: true, receipt: response.data });
            }
        });
    }
    else // otp not correct
    {
        console.log(' ...authorisation problem!');
        res.send({ ok: false });
    }
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE info
//
exports.info = function info(req, res)  {
    res.render('info', {manifest:manifest});
}

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE end
//

var finServ = request.newClient(config.finalServURI);

exports.close = function close(req, res)  {
    res.send({ ok: true, info: "triggered to close the election" });
    console.log('Closing election.');
    console.log('Getting the result...');
    server.getResult(function(err, result) {
        if (err) {
            console.log(' ...Internal error. Cannot fetch the result: ', err);
        }
        else {
            console.log('Result:', result);
            console.log('Sending result to the final server');
            var data = {data: result}
            finServ.post('data', data, function(err, otp_res, body){
                if (err) {
                    console.log(' ...Error: Cannot send the result to the final server: ', err);
                }
                else {
                    console.log(' ...Result sent to the final server.');
                    console.log(' ...Response:', body);
                }
            });
        }
    })
}

<commit_msg>[Collecting server app] otp reset<commit_after>var request = require('request-json');
var config = require('./config');
var manifest = require('./manifest');
var server = require('./server');
var sendEmail = require('./sendEmail');

///////////////////////////////////////////////////////////////////////////////////////

// OTP STORE

var otp_store = {};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE otp
//

exports.otp = function otp(req, res)
{
    var email = req.body.email;
    if (email) {
        if (!server.eligibleVoters[email]) // Check if the voter is eligible
        {
            console.log('Voter not eligible', email);
            res.send({ ok: false, descr: 'Voter not eligible' });
        }
        else // eligible voter create a fresh OTP and send it
        {
            // Get a fresh otp (Java async call)
            console.log('Obtaining OTP...');
            server.getFreshOTP(function(err, otp) {
                if (err) {
                    console.log(' ...Internal error:', err);
                    res.send({ ok: false, descr: 'Internal error. Cannot obtain an OTP' });
                    return;
                }
                // We have a fresh otp
                console.log(' ...Obtained a fresh OTP: ', otp);
                otp_store[email] = otp // store the opt under the voter id (email)
                // schedule reset of the otp
                setTimeout( function(){ otp_store[email]=null; }, 10*60000); // 10 min

                // Send an email
                /*
                console.log('Sending an emal with otp to', email, otp);
                sendEmail(email, 'Your One Time Password for sElect', otp, function (err,info) {
                    if (err) {
                        console.log(' ...Error:', err);
                    }else{
                        console.log(' ...E-mail sent: ' + info.response);
                    }
                    res.send({ ok: true });
                })
                */
                res.send({ ok: true }); // TODO: this is nestead of tha above
            });
        }
    }
    else
        res.send({ ok: false });
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE cast
//

exports.cast = function cast(req, res)
{
    var email = req.body.email;
    var otp = req.body.otp;
    var ballot = req.body.ballot;
    console.log('BALLOT COMING:', email, otp, ballot);

    // make sure that we have all the pieces:
    if (!email || !otp || !ballot ) {
        res.send({ ok: false });
        return;
    }

    // Check the otp (and, implicitly, the identifier)
    console.log('Checking the otp for a voter:', email, otp);
    if (otp_store[email] === otp) {
        console.log(' ...otp correct');

        // Cast the ballot:
        console.log('CollectBallot for', email );
        server.collectBallot(email, ballot, function(err, response) {
            if (err) {
                console.log(' ...Internal error: ', err);
                res.send({ ok: false, descr: 'Internal error' });
            }
            else if (response.data=='') {
                console.log(' ...Ballot rejected: ' );
                res.send({ ok: false, descr: 'Ballot rejected' });
                return;
            }
            else { // everything ok
                // TODO Now the result has a different form
                console.log(' ...Balloc accepted. Response = ', response);
                res.send({ ok: true, receipt: response.data });
            }
        });
    }
    else // otp not correct
    {
        console.log(' ...authorisation problem!');
        res.send({ ok: false });
    }
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE info
//
exports.info = function info(req, res)  {
    res.render('info', {manifest:manifest});
}

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE end
//

var finServ = request.newClient(config.finalServURI);

exports.close = function close(req, res)  {
    res.send({ ok: true, info: "triggered to close the election" });
    console.log('Closing election.');
    console.log('Getting the result...');
    server.getResult(function(err, result) {
        if (err) {
            console.log(' ...Internal error. Cannot fetch the result: ', err);
        }
        else {
            console.log('Result:', result);
            console.log('Sending result to the final server');
            var data = {data: result}
            finServ.post('data', data, function(err, otp_res, body){
                if (err) {
                    console.log(' ...Error: Cannot send the result to the final server: ', err);
                }
                else {
                    console.log(' ...Result sent to the final server.');
                    console.log(' ...Response:', body);
                }
            });
        }
    })
}

<|endoftext|>


<commit_before>var fs = require('fs');
var request = require('request-json');
var config = require('./config');
var manifest = require('./manifest');
var server = require('./server');
var sendEmail = require('./sendEmail');

///////////////////////////////////////////////////////////////////////////////////////
// State

var otp_store = {};

var resultReady = fs.existsSync(config.RESULT_FILE);
var active = !resultReady; // active = accepts ballots

if (fs.existsSync(config.ACCEPTED_BALLOTS_LOG_FILE)) {
    console.log('Resuming (reading already accepted ballots)');
    var log_data = fs.readFileSync(config.ACCEPTED_BALLOTS_LOG_FILE, {flags:'r', encoding:'utf8'});
    log_data.split('\n').forEach(function (entry) { // for each entry in the log file
        if (entry==='') return;
        entry = JSON.parse(entry);
        console.log('  - processing a ballot of', entry.email);
        server.collectBallotSync(entry.email, entry.ballot);
    });
}

var log = fs.createWriteStream(config.ACCEPTED_BALLOTS_LOG_FILE, {flags:'a', encoding:'utf8'});

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE otp
//

exports.otp = function otp(req, res)
{
    if (!active) {
        console.log('ERROR: otp request, but election is closed.')
        res.send({ ok: false, descr: 'Election closed' });
        return;
    }

    var email = req.body.email;
    if (email) {
        if (!server.eligibleVoters[email]) // Check if the voter is eligible
        {
            console.log('Voter not eligible', email);
            res.send({ ok: false, descr: 'Invalid voter identifier (e-mail)' });
        }
        else // eligible voter create a fresh OTP and send it
        {
            // Get a fresh otp (Java async call)
            console.log('Obtaining OTP...');
            server.getFreshOTP(function(err, otp) {
                if (err) {
                    console.log(' ...Internal error:', err);
                    res.send({ ok: false, descr: 'Internal error. Cannot obtain an OTP' });
                    return;
                }
                // We have a fresh otp
                console.log(' ...Obtained a fresh OTP: ', otp);
                otp_store[email] = otp // store the opt under the voter id (email)
                // schedule reset of the otp
                setTimeout( function(){ otp_store[email]=null; }, 10*60000); // 10 min

                // Send an email
                /*
                console.log('Sending an emal with otp to', email, otp);
                sendEmail(email, 'Your One Time Password for sElect', otp, function (err,info) {
                    if (err) {
                        console.log(' ...Error:', err);
                    }else{
                        console.log(' ...E-mail sent: ' + info.response);
                    }
                    res.send({ ok: true });
                })
                */
                res.send({ ok: true }); // TODO: this is nestead of tha above
            });
        }
    }
    else
        res.send({ ok: false, descr: 'Empty e-mail address' });
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE cast
//

exports.cast = function cast(req, res)
{
    if (!active) {
        console.log('ERROR: ballot comming, but election is closed.')
        res.send({ ok: false, descr: 'Election closed' });
        return;
    }

    var email = req.body.email;
    var otp = req.body.otp;
    var ballot = req.body.ballot;
    console.log('BALLOT COMING:', email, otp, ballot);

    // make sure that we have all the pieces:
    if (!email || !otp || !ballot ) {
        res.send({ ok: false, descr: 'Wrong request' });
        return;
    }

    // Check the otp (and, implicitly, the identifier)
    console.log('Checking the otp for a voter:', email, otp);
    if (otp_store[email] === otp) {
        console.log(' ...otp correct');

        // Cast the ballot:
        console.log('CollectBallot for', email );
        server.collectBallot(email, ballot, function(err, response) {
            if (err) {
                console.log(' ...Internal error: ', err);
                res.send({ ok: false, descr: 'Internal error' });
            }
            else if (!response.ok) {
                console.log(' ...Ballot rejected: ', response.data);
                res.send({ ok: false, descr: response.data });
            }
            else { // everything ok
                console.log(' ...Balloc accepted. Response = ', response);
                res.send({ ok: true, receipt: response.data });
                // log the accepted ballot
                log.write(JSON.stringify({ email:email, ballot:ballot })+'\n', null,
                          function whenFlushed(e,r) {
                    console.log('Ballot for %s logged', email);
                });
                // TODO: how to make sure that this stream is flushed right away
            }
        });
    }
    else // otp not correct
    {
        console.log(' ...Invalid OTP');
        res.send({ ok: false, descr: 'Invalid OTP (one time password)' });
        // if an invalid otp is given, we require that a new otp be generated (reset otp):
        otp_store[email] = null;
    }
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE info
//
exports.info = function info(req, res)  {
    res.render('info', {manifest:manifest, active: active, resultReady: resultReady});
}

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE close
//

var finserv_options = {};
if (config.ignore_fin_serv_cert)
    finserv_options = {rejectUnauthorized: false};

// Save result in a file
function saveResult(result) {
    fs.writeFile(config.RESULT_FILE, result, function (err) {
        if (err)
            console.log('Problems with saving result', config.RESULT_FILE);
        else {
            console.log('Result saved in', config.RESULT_FILE);
            resultReady = true;
        }
    });
}

// Send result to the final server
function sendResult(result) {
    console.log('Sending result to the final server');
    var finServ = request.newClient(manifest.finalServer.URI, finserv_options);
    var data = {data: result}
    finServ.post('data', data, function(err, otp_res, body) {
        if (err) {
            console.log(' ...Error: Cannot send the result to the final server: ', err);
        }
        else {
            console.log(' ...Result sent to the final server.');
            console.log(' ...Response:', body);
        }
    });
    // TODO: should we somewhow close the connection to the final server?
}

exports.close = function close(req, res)  {
    if (!active) {
        res.send({ ok: false, info: "election already closed" });
        return;
    }
    active = false;
    res.send({ ok: true, info: "triggered to close the election" });
    console.log('Closing election.');
    console.log('Getting the result...');
    server.getResult(function(err, result) {
        if (err) {
            console.log(' ...Internal error. Cannot fetch the result: ', err);
        }
        else {
            console.log('Result:', result);
            sendResult(result);
            saveResult(result);
        }
    });
}

///////////////////////////////////////////////////////////////////////////////////////
// Serve a particular static file
exports.serveFile = function serveFile(path) {
    return function (req, res) {
        fs.exists(path, function(exists) {
            if (exists) {
                fs.createReadStream(path).pipe(res);
            } else {
                res.status(404).send('404: Not found');
            }
        });
    }
}

<commit_msg>generate otp in js<commit_after>var fs = require('fs');
var request = require('request-json');
var config = require('./config');
var manifest = require('./manifest');
var server = require('./server');
var sendEmail = require('./sendEmail');
var crypto = require('cryptofunc');

///////////////////////////////////////////////////////////////////////////////////////
// State

var otp_store = {};

var resultReady = fs.existsSync(config.RESULT_FILE);
var active = !resultReady; // active = accepts ballots

if (fs.existsSync(config.ACCEPTED_BALLOTS_LOG_FILE)) {
    console.log('Resuming (reading already accepted ballots)');
    var log_data = fs.readFileSync(config.ACCEPTED_BALLOTS_LOG_FILE, {flags:'r', encoding:'utf8'});
    log_data.split('\n').forEach(function (entry) { // for each entry in the log file
        if (entry==='') return;
        entry = JSON.parse(entry);
        console.log('  - processing a ballot of', entry.email);
        server.collectBallotSync(entry.email, entry.ballot);
    });
}

var log = fs.createWriteStream(config.ACCEPTED_BALLOTS_LOG_FILE, {flags:'a', encoding:'utf8'});

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE otp
//

exports.otp = function otp(req, res)
{
    if (!active) {
        console.log('ERROR: otp request, but election is closed.')
        res.send({ ok: false, descr: 'Election closed' });
        return;
    }

    var email = req.body.email;
    if (email) {
        if (!server.eligibleVoters[email]) // Check if the voter is eligible
        {
            console.log('Voter not eligible', email);
            res.send({ ok: false, descr: 'Invalid voter identifier (e-mail)' });
        }
        else // eligible voter create a fresh OTP and send it
        {
            // Generate a fresh otp
            var otp = crypto.nonce();
            console.log(' ...Fresh OTP: ', otp);
            otp_store[email] = otp // store the opt under the voter id (email)
            // schedule reset of the otp
            setTimeout( function(){ otp_store[email]=null; }, 10*60000); // 10 min

            // Send an email
            /*
            console.log('Sending an emal with otp to', email, otp);
            sendEmail(email, 'Your One Time Password for sElect', otp, function (err,info) {
                if (err) {
                    console.log(' ...Error:', err);
                }else{
                    console.log(' ...E-mail sent: ' + info.response);
                }
                res.send({ ok: true });
            })
            */
            res.send({ ok: true }); // TODO: this is nestead of tha above
        }
    }
    else
        res.send({ ok: false, descr: 'Empty e-mail address' });
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE cast
//

exports.cast = function cast(req, res)
{
    if (!active) {
        console.log('ERROR: ballot comming, but election is closed.')
        res.send({ ok: false, descr: 'Election closed' });
        return;
    }

    var email = req.body.email;
    var otp = req.body.otp;
    var ballot = req.body.ballot;
    console.log('BALLOT COMING:', email, otp, ballot);

    // make sure that we have all the pieces:
    if (!email || !otp || !ballot ) {
        res.send({ ok: false, descr: 'Wrong request' });
        return;
    }

    // Check the otp (and, implicitly, the identifier)
    console.log('Checking the otp for a voter:', email, otp);
    if (otp_store[email] === otp) {
        console.log(' ...otp correct');

        // Cast the ballot:
        console.log('CollectBallot for', email );
        server.collectBallot(email, ballot, function(err, response) {
            if (err) {
                console.log(' ...Internal error: ', err);
                res.send({ ok: false, descr: 'Internal error' });
            }
            else if (!response.ok) {
                console.log(' ...Ballot rejected: ', response.data);
                res.send({ ok: false, descr: response.data });
            }
            else { // everything ok
                console.log(' ...Balloc accepted. Response = ', response);
                res.send({ ok: true, receipt: response.data });
                // log the accepted ballot
                log.write(JSON.stringify({ email:email, ballot:ballot })+'\n', null,
                          function whenFlushed(e,r) {
                    console.log('Ballot for %s logged', email);
                });
                // TODO: how to make sure that this stream is flushed right away
            }
        });
    }
    else // otp not correct
    {
        console.log(' ...Invalid OTP');
        res.send({ ok: false, descr: 'Invalid OTP (one time password)' });
        // if an invalid otp is given, we require that a new otp be generated (reset otp):
        otp_store[email] = null;
    }
};

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE info
//
exports.info = function info(req, res)  {
    res.render('info', {manifest:manifest, active: active, resultReady: resultReady});
}

///////////////////////////////////////////////////////////////////////////////////////
// ROUTE close
//

var finserv_options = {};
if (config.ignore_fin_serv_cert)
    finserv_options = {rejectUnauthorized: false};

// Save result in a file
function saveResult(result) {
    fs.writeFile(config.RESULT_FILE, result, function (err) {
        if (err)
            console.log('Problems with saving result', config.RESULT_FILE);
        else {
            console.log('Result saved in', config.RESULT_FILE);
            resultReady = true;
        }
    });
}

// Send result to the final server
function sendResult(result) {
    console.log('Sending result to the final server');
    var finServ = request.newClient(manifest.finalServer.URI, finserv_options);
    var data = {data: result}
    finServ.post('data', data, function(err, otp_res, body) {
        if (err) {
            console.log(' ...Error: Cannot send the result to the final server: ', err);
        }
        else {
            console.log(' ...Result sent to the final server.');
            console.log(' ...Response:', body);
        }
    });
    // TODO: should we somewhow close the connection to the final server?
}

exports.close = function close(req, res)  {
    if (!active) {
        res.send({ ok: false, info: "election already closed" });
        return;
    }
    active = false;
    res.send({ ok: true, info: "triggered to close the election" });
    console.log('Closing election.');
    console.log('Getting the result...');
    server.getResult(function(err, result) {
        if (err) {
            console.log(' ...Internal error. Cannot fetch the result: ', err);
        }
        else {
            console.log('Result:', result);
            sendResult(result);
            saveResult(result);
        }
    });
}

///////////////////////////////////////////////////////////////////////////////////////
// Serve a particular static file
exports.serveFile = function serveFile(path) {
    return function (req, res) {
        fs.exists(path, function(exists) {
            if (exists) {
                fs.createReadStream(path).pipe(res);
            } else {
                res.status(404).send('404: Not found');
            }
        });
    }
}

<|endoftext|>


<commit_before>(function($) {
  jQuery.fn.lineeditor = function (options, callback) {
    return this.each(function () {
      var el = this;
      var $el = $(this);

      if (el.nodeName.toLowerCase() != 'textarea') { return; }

      var hidden = $('<input type="hidden">').attr('id', el.id).attr('name',el.name).val($el.val());
      var container = $('

');
      var val = $el.val();

      var buttons = $('');
      $.each({
        'delete': removeLine,
        'moveup': move('up'),
        'movedown': move('down')
      },function(k,v){
        var node = $('' + k + '').click(v);
        buttons.append(node);
      });

      hidden.val(val);
      container.append(hidden);

      container.append($('Add Line').click(addLine));

      $.each(val.split("\n"),function(){
        addLine(this);
      });

      $el.replaceWith(container);
      calculatePositioning();

      function addLine(value) {
        value = (value && value.target ? value.preventDefault() && '' : value);
        var input = $('<input type="text">').val(value?value.toString():'').change(processValues).keyup(processValues).keypress(processValues);
        var input_container = $('');
        container.find('a:last').before(input_container.append(input).append(buttons.clone(true)));
        processValues();
        calculatePositioning();
        return input;
      }

      function removeLine(ev) {
        ev.preventDefault();
        $(this).closest('.input').remove();
        processValues();
      }

      function move(direction) {
        return (function(ev) {
          ev.preventDefault();
          var target = $(this).closest('.input');
          if (direction == 'down') {
            target.insertAfter(target.next('.input'));
          } else if ( direction == 'up' ) {
            target.insertBefore(target.prev('.input'));
          }
          calculatePositioning();
        })
      }

      function processValues(e) {
        if (e && e.keyCode == 9) {
          e.preventDefault();
          target = $(e.target).closest('.input');
          if (!e.shiftKey) {
            target.nextAll('.input:first').find('input[type=text]:first').focus().size() || addLine().focus();
          } else {
            target.prevAll('.input:first').find('input[type=text]').focus();
          }
          return;
        }
        hidden.val(
          container.find('input[type=text]').map(function(){
            return $(this).val();
          }).toArray().join("\n").trim()
        );
      }

      function calculatePositioning() {
        container.find('.input').each(function(){
          if ( $(this).prev('.input').size() ) {
            $(this).find('.moveup').size() || $(this).find('.buttons').append(buttons.find('.moveup').clone(true));
          } else {
            $(this).find('.moveup').remove();
          }

          if ( $(this).next('.input').size() ) {
             $(this).find('.movedown').size() || $(this).find('.buttons').append(buttons.find('.movedown').clone(true));
          } else {
            $(this).find('.movedown').remove();
          }

          $(this).width($(this).find('input[type=text]').outerWidth());
        })
      }

    })
  }
})(jQuery);<commit_msg>Boom magic lineeditor.<commit_after>(function($) {
  jQuery.fn.lineeditor = function () {
    return this.each(function () {
      if ( this.liveeditor ) {
        return this.liveeditor;
      }

      this.liveeditor = function() {
        if (this.nodeName.toLowerCase() != 'textarea') { return; }

        var el = {};
        $el = $(this);

        el.addLine = function(value) {
          value = (value && value.target ? value.preventDefault() && '' : value);
          var input = $('<input type="text">').val(value?value.toString():'').change(el.processValues).keyup(el.processValues).keypress(el.processValues);
          var input_container = $('');
          el.container.find('a:last').before(input_container.append(input).append(el.buttons.clone(true)));
          el.processValues();
          el.calculatePositioning();
          return input;
        }

        el.removeLine = function(ev) {
          ev.preventDefault();
          $(this).closest('.input').remove();
          el.processValues();
        }

        el.move = function(direction) {
          return (function(ev) {
            ev.preventDefault();
            var target = $(this).closest('.input');
            if (direction == 'down') {
              target.insertAfter(target.next('.input'));
            } else if ( direction == 'up' ) {
              target.insertBefore(target.prev('.input'));
            }
            el.calculatePositioning();
          })
        }

        el.processValues = function(e) {
          if (e && e.keyCode == 9) {
            e.preventDefault();
            target = $(e.target).closest('.input');
            if (!e.shiftKey) {
              target.nextAll('.input:first').find('input[type=text]:first').focus().size() || el.addLine().focus();
            } else {
              target.prevAll('.input:first').find('input[type=text]').focus();
            }
            return;
          }
          el.hidden.val(
            el.container.find('input[type=text]').map(function(){
              return $(this).val();
            }).toArray().join("\n").trim()
          );
        }

        el.calculatePositioning = function() {
          el.container.find('.input').each(function(){
            if ( $(this).prev('.input').size() ) {
              $(this).find('.moveup').size() || $(this).find('.buttons').append(el.buttons.find('.moveup').clone(true));
            } else {
              $(this).find('.moveup').remove();
            }

            if ( $(this).next('.input').size() ) {
               $(this).find('.movedown').size() || $(this).find('.buttons').append(el.buttons.find('.movedown').clone(true));
            } else {
              $(this).find('.movedown').remove();
            }

            $(this).width($(this).find('input[type=text]').outerWidth());
          })
        }

        el.$el = $el;

        el.hidden = $('<input type="hidden">').attr('id', this.id).attr('name',this.name).val($el.val());
        el.container = $('

');
        el.val = $el.val();

        el.buttons = $('');
        $.each({
          'delete': el.removeLine,
          'moveup': el.move('up'),
          'movedown': el.move('down')
        },function(k,v){
          var node = $('' + k + '').click(v);
          el.buttons.append(node);
        });

        el.hidden.val(el.val);
        el.container.append(el.hidden);

        el.container.append($('Add Line').click(el.addLine));
        $.each(el.val.split("\n"),function(){
          el.addLine(this);
        });

        $el.replaceWith(el.container);
        el.calculatePositioning();

      }
      return this.liveeditor();
    })
  }
})(jQuery);<|endoftext|>


<commit_before>import React from 'react'

import Checkbox from 'material-ui/lib/checkbox';
import List from 'material-ui/lib/lists/list';
import ListItem from 'material-ui/lib/lists/list-item';
import Divider from 'material-ui/lib/divider';

export class FacetListItem extends React.Component {

  constructor(props) {
      super(props)

      this.onCheck = this._onCheck.bind(this)
  }

  _onCheck(event, checked) {
    if (this.props.onCheck){
      this.props.onCheck(this.props.value, checked)
    }
  }

  render() {
    return (
      <ListItem value={this.props.value}
        leftCheckbox={ <Checkbox value={this.props.value} checked={this.props.selected} onCheck={this.onCheck} /> }
        primaryText={ {this.props.value} ({this.props.count}) }
      />
    )
  }
}

export class FacetList extends React.Component {
    constructor(props) {
        super(props);

        this.onCheck = this._onCheck.bind(this);
    }

    _onCheck(category, key, value) {
        this.props.onChange(category, key, value);
    }

    render() {
        const { facets, filters } = this.props
        var self = this;

        if (!facets) {
            return (

)
        }

        var buildings = null;
        if (facets.building && facets.building.items && facets.building.items.length) {
            buildings =
              <List subheader="Building" >
                  {facets.building.items.map(function(item) {
                      var selected = (filters.building === item.key);
                      return <FacetListItem key={item.key} value={item.key} count={item.docCount} selected={selected} onCheck={self.onCheck.bind(self, "building")} />
                  })}
              </List>
        }

        var departments = null;
        if (facets.department && facets.department.items && facets.department.items.length) {
            departments =
              <List subheader="Department" >
                  {facets.department.items.map(function(item) {
                      var selected = (filters.department === item.key);
                      return <FacetListItem key={item.key} value={item.key} count={item.docCount} selected={selected} onCheck={self.onCheck.bind(self, "department")} />
                  })}
              </List>
        }

        var vlans = null;
        if (facets.vlan && facets.vlan.items && facets.vlan.items.length) {
            vlans =
              <List subheader="VLAN" >
                  {facets.vlan.items.map(function(item) {
                      var selected = (filters.vlan === item.key);
                      return <FacetListItem key={item.key} value={item.key} count={item.docCount} selected={selected} onCheck={self.onCheck.bind(self, "vlan")} />
                  })}
              </List>
        }

        return (

                { buildings }
                <Divider />
                { departments }
                <Divider />
                { vlans }

        )
    }
}
<commit_msg>unused<commit_after><|endoftext|>


<commit_before>import React from 'react';

import SearchInput from './searchInput';

import styles from './splash.scss';

export default class Splash extends React.Component {
  render() {
    return (

NamstR

          <SearchInput />

a CA&ES DO production

    );
  }
}
<commit_msg>get new path<commit_after>import React from 'react';

import SearchInput from '../search/input';

import styles from './splash.scss';

export default class Splash extends React.Component {
  render() {
    return (

NamstR

          <SearchInput />

a CA&ES DO production

    );
  }
}
<|endoftext|>


<commit_before>/**
 * @file TableContentTable component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import BaseTable from '../_BaseTable';

import Theme from '../Theme';
import TableFragment from '../_statics/TableFragment';
import HorizontalAlign from '../_statics/HorizontalAlign';
import SelectMode from '../_statics/SelectMode';
import SelectAllMode from '../_statics/SelectAllMode';
import SortingType from '../_statics/SortingType';

import Util from '../_vendors/Util';

class TableContentTable extends Component {

    static Fragment = TableFragment;
    static Align = HorizontalAlign;
    static Fixed = HorizontalAlign;
    static SelectMode = SelectMode;
    static SelectAllMode = SelectAllMode;
    static SortingType = SortingType;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {
            className, style, columns, data, isHeadFixed, isFootFixed, isPaginated,
            fixedHeadHeight, fixedFootHeight, bodyScrollerHeight,
            ...restProps
        } = this.props;

        return (

                {
                    isHeadFixed ?
                        <BaseTable {...restProps}
                                   className="table-content-fixed-head"
                                   style={fixedHeadHeight ? {height: fixedHeadHeight} : null}
                                   fragment={TableFragment.HEAD}
                                   columns={columns}
                                   data={data}
                                   isPaginated={isPaginated}/>
                        :
                        null
                }

                        <BaseTable {...restProps}
                                   className="table-content-body"
                                   columns={columns}
                                   data={data}
                                   isPaginated={isPaginated}/>

                {
                    isFootFixed ?
                        <BaseTable {...restProps}
                                   className="table-content-fixed-foot"
                                   style={fixedFootHeight ? {height: fixedFootHeight} : null}
                                   fragment={TableFragment.FOOT}
                                   columns={columns}
                                   data={data}
                                   isPaginated={isPaginated}/>
                        :
                        null
                }

        );

    }
}

TableContentTable.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The theme of the table select radio or checkbox.
     */
    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The select mode of table.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * The select all mode of table, all or current page.
     */
    selectAllMode: PropTypes.oneOf(Util.enumerateValue(SelectAllMode)),

    /**
     * Children passed into table header.
     */
    columns: PropTypes.arrayOf(PropTypes.shape({

        /**
         * fixed position of column ( true / 'left' / 'right' )
         */
        fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(Util.enumerateValue(HorizontalAlign))]),

        /**
         * width of column
         */
        width: PropTypes.number,

        /**
         * minimum width of column
         */
        minWidth: PropTypes.number,

        /**
         * The class name of header.
         */
        headClassName: PropTypes.string,

        /**
         * Override the styles of header.
         */
        headStyle: PropTypes.object,

        /**
         * The render content in header.
         * (1) string，example： 'id'
         * (2) callback，example：function (colIndex) {return colIndex;}
         */
        headRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table header cell
         */
        headAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table header
         */
        headSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of td.
         */
        bodyClassName: PropTypes.string,

        /**
         * Override the styles of td.
         */
        bodyStyle: PropTypes.object,

        /**
         * The render content in table.
         * (1) data key，example： 'id'
         * (2) data key tamplate，example：'${id} - ${name}'
         * (3) callback，example：function (rowData, rowIndex, colIndex) {return rowData.id;}
         */
        bodyRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table body cell
         */
        bodyAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table body
         */
        bodySpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of footer.
         */
        footClassName: PropTypes.string,

        /**
         * Override the styles of footer.
         */
        footStyle: PropTypes.object,

        /**
         * The render content in footer.
         * (1) string，example： 'id'
         * (2) callback，example：function (colIndex) {return colIndex;}
         */
        footRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table footer cell
         */
        footAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table foot
         */
        footSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * If true,this column can be sorted.
         */
        sortable: PropTypes.bool,

        /**
         * Sorting property.
         */
        sortingProp: PropTypes.string,

        defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType))

    })).isRequired,

    data: PropTypes.array,

    /**
     * sorting
     */
    sorting: PropTypes.shape({
        prop: PropTypes.string,
        type: PropTypes.oneOf(Util.enumerateValue(SortingType))
    }),
    defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType)),
    sortingAscIconCls: PropTypes.string,
    sortingDescIconCls: PropTypes.string,
    sortingFunc: PropTypes.func,

    /**
     * pagination
     */
    isPaginated: PropTypes.bool,
    pagination: PropTypes.shape({
        pageSize: PropTypes.number,
        page: PropTypes.number
    }),
    pageSizes: PropTypes.array,

    /**
     * fixed
     */
    isHeadFixed: PropTypes.bool,
    isFootFixed: PropTypes.bool,

    fixedHeadHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    fixedFootHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    bodyScrollerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

    /**
     * callback
     */
    onSortChange: PropTypes.func

};

TableContentTable.defaultProps = {

    selectMode: SelectMode.SINGLE_SELECT,
    selectAllMode: SelectAllMode.CURRENT_PAGE,

    defaultSortingType: SortingType.ASC,
    sortingAscIconCls: 'fas fa-angle-up',
    sortingDescIconCls: 'fas fa-angle-down',

    isPaginated: true,
    pagination: {
        pageSize: 10,
        page: 0
    },
    pageSizes: [5, 10, 15, 20],

    isHeadFixed: false,
    isFootFixed: false

};

export default TableContentTable;
<commit_msg>update TableContentTable<commit_after>/**
 * @file TableContentTable component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import BaseTable from '../_BaseTable';

import Theme from '../Theme';
import TableFragment from '../_statics/TableFragment';
import HorizontalAlign from '../_statics/HorizontalAlign';
import SelectMode from '../_statics/SelectMode';
import SelectAllMode from '../_statics/SelectAllMode';
import SortingType from '../_statics/SortingType';

import Util from '../_vendors/Util';

class TableContentTable extends Component {

    static Fragment = TableFragment;
    static Align = HorizontalAlign;
    static Fixed = HorizontalAlign;
    static SelectMode = SelectMode;
    static SelectAllMode = SelectAllMode;
    static SortingType = SortingType;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {
            className, style, columns, data, isHeadFixed, isFootFixed, isPaginated,
            ...restProps
        } = this.props;

        return (

                {
                    isHeadFixed ?
                        <BaseTable {...restProps}
                                   className="table-content-fixed-head"
                                   fragment={TableFragment.HEAD}
                                   columns={columns}
                                   data={data}
                                   isPaginated={isPaginated}/>
                        :
                        null
                }

                        <BaseTable {...restProps}
                                   className="table-content-body"
                                   columns={columns}
                                   data={data}
                                   isPaginated={isPaginated}/>

                {
                    isFootFixed ?
                        <BaseTable {...restProps}
                                   className="table-content-fixed-foot"
                                   fragment={TableFragment.FOOT}
                                   columns={columns}
                                   data={data}
                                   isPaginated={isPaginated}/>
                        :
                        null
                }

        );

    }
}

TableContentTable.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * The theme of the table select radio or checkbox.
     */
    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The select mode of table.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    /**
     * The select all mode of table, all or current page.
     */
    selectAllMode: PropTypes.oneOf(Util.enumerateValue(SelectAllMode)),

    /**
     * Children passed into table header.
     */
    columns: PropTypes.arrayOf(PropTypes.shape({

        /**
         * fixed position of column ( true / 'left' / 'right' )
         */
        fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(Util.enumerateValue(HorizontalAlign))]),

        /**
         * width of column
         */
        width: PropTypes.number,

        /**
         * minimum width of column
         */
        minWidth: PropTypes.number,

        /**
         * The class name of header.
         */
        headClassName: PropTypes.string,

        /**
         * Override the styles of header.
         */
        headStyle: PropTypes.object,

        /**
         * The render content in header.
         * (1) string，example： 'id'
         * (2) callback，example：function (colIndex) {return colIndex;}
         */
        headRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table header cell
         */
        headAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table header
         */
        headSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of td.
         */
        bodyClassName: PropTypes.string,

        /**
         * Override the styles of td.
         */
        bodyStyle: PropTypes.object,

        /**
         * The render content in table.
         * (1) data key，example： 'id'
         * (2) data key tamplate，example：'${id} - ${name}'
         * (3) callback，example：function (rowData, rowIndex, colIndex) {return rowData.id;}
         */
        bodyRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table body cell
         */
        bodyAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table body
         */
        bodySpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * The class name of footer.
         */
        footClassName: PropTypes.string,

        /**
         * Override the styles of footer.
         */
        footStyle: PropTypes.object,

        /**
         * The render content in footer.
         * (1) string，example： 'id'
         * (2) callback，example：function (colIndex) {return colIndex;}
         */
        footRenderer: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

        /**
         * align of table footer cell
         */
        footAlign: PropTypes.oneOf(Util.enumerateValue(HorizontalAlign)),

        /**
         * column span of table foot
         */
        footSpan: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

        /**
         * If true,this column can be sorted.
         */
        sortable: PropTypes.bool,

        /**
         * Sorting property.
         */
        sortingProp: PropTypes.string,

        defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType))

    })).isRequired,

    data: PropTypes.array,

    /**
     * sorting
     */
    sorting: PropTypes.shape({
        prop: PropTypes.string,
        type: PropTypes.oneOf(Util.enumerateValue(SortingType))
    }),
    defaultSortingType: PropTypes.oneOf(Util.enumerateValue(SortingType)),
    sortingAscIconCls: PropTypes.string,
    sortingDescIconCls: PropTypes.string,
    sortingFunc: PropTypes.func,

    /**
     * pagination
     */
    isPaginated: PropTypes.bool,
    pagination: PropTypes.shape({
        pageSize: PropTypes.number,
        page: PropTypes.number
    }),
    pageSizes: PropTypes.array,

    /**
     * fixed
     */
    isHeadFixed: PropTypes.bool,
    isFootFixed: PropTypes.bool,

    /**
     * callback
     */
    onSortChange: PropTypes.func

};

TableContentTable.defaultProps = {

    selectMode: SelectMode.SINGLE_SELECT,
    selectAllMode: SelectAllMode.CURRENT_PAGE,

    defaultSortingType: SortingType.ASC,
    sortingAscIconCls: 'fas fa-angle-up',
    sortingDescIconCls: 'fas fa-angle-down',

    isPaginated: true,
    pagination: {
        pageSize: 10,
        page: 0
    },
    pageSizes: [5, 10, 15, 20],

    isHeadFixed: false,
    isFootFixed: false

};

export default TableContentTable;
<|endoftext|>