<commit_before>'use strict';

const fs = require('fs');
const path = require('path');

const barbe = require('barbe');
const _ = require('lodash');

const { isNumber } = require('@terascope/utils');

const { safeEncode } = require('../../../../../utils/encoding_utils');
const { setMaxOldSpaceViaEnv } = require('./utils');

class K8sResource {
    /**
     * K8sResource allows the generation of k8s resources based on templates.
     * After creating the object, the k8s resource is accessible on the objects
     * .resource property.
     *
     * @param {String} resourceType - jobs/services/deployments
     * @param {String} resourceName - worker/execution_controller
     * @param {Object} terasliceConfig - teraslice cluster config from context
     * @param {Object} execution - teraslice execution
     */
    constructor(resourceType, resourceName, terasliceConfig, execution) {
        this.execution = execution;
        this.jobLabelPrefix = 'job.teraslice.terascope.io';
        this.jobPropertyLabelPrefix = 'job-property.teraslice.terascope.io';
        this.nodeType = resourceName;
        this.terasliceConfig = terasliceConfig;

        if (resourceName === 'worker') {
            this.nameInfix = 'wkr';
        } else if (resourceName === 'execution_controller') {
            this.nameInfix = 'exc';
        } else {
            throw new Error(`Unsupported resourceName: ${resourceName}`);
        }

        this.templateGenerator = this._makeTemplate(resourceType, resourceName);
        this.templateConfig = this._makeConfig();
        this.resource = this.templateGenerator(this.templateConfig);

        this._setJobLabels();

        // Apply job `targets` setting as k8s nodeAffinity
        // We assume that multiple targets require both to match ...
        // NOTE: If you specify multiple `matchExpressions` associated with
        // `nodeSelectorTerms`, then the pod can be scheduled onto a node
        // only if *all* `matchExpressions` can be satisfied.
        this._setTargets();
        this._setResources();
        this._setVolumes();
        this._setAssetsVolume();
        this._setImagePullSecret();
        this._setEphemeralStorage();
        this._setExternalPorts();
        this._setPriorityClassName();

        if (resourceName === 'worker') {
            this._setWorkerAntiAffinity();
        }

        // Execution controller targets are required nodeAffinities, if
        // required job targets are also supplied, then *all* of the matches
        // will have to be satisfied for the job to be scheduled.  This also
        // adds tolerations for any specified targets
        if (resourceName === 'execution_controller') {
            this._setExecutionControllerTargets();
        }
    }

    _makeConfig() {
        const clusterName = _.get(this.terasliceConfig, 'name');
        const clusterNameLabel = clusterName.replace(/[^a-zA-Z0-9_\-.]/g, '_').substring(0, 63);
        const configMapName = _.get(
            this.terasliceConfig,
            'kubernetes_config_map_name',
            `${this.terasliceConfig.name}-worker`
        );
        const dockerImage = this.execution.kubernetes_image
            || this.terasliceConfig.kubernetes_image;
        // name needs to be a valid DNS name since it is used in the svc name,
        // so we can only permit alphanumeric and - characters.  _ is forbidden.
        // -> regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?'
        const jobNameLabel = this.execution.name
            .toLowerCase()
            .replace(/[^a-zA-Z0-9\-.]/g, '-')
            .replace(/^[^a-z]/, 'a')
            .replace(/[^a-z0-9]$/, '0')
            .substring(0, 63);
        const name = `ts-${this.nameInfix}-${jobNameLabel.substring(0, 35)}-${this.execution.job_id.substring(0, 13)}`;
        const shutdownTimeoutMs = _.get(this.terasliceConfig, 'shutdown_timeout', 60000);
        const shutdownTimeoutSeconds = Math.round(shutdownTimeoutMs / 1000);

        const config = {
            // assetsDirectory: _.get(this.terasliceConfig, 'assets_directory', ''),
            // assetsVolume: _.get(this.terasliceConfig, 'assets_volume', ''),
            clusterName,
            clusterNameLabel,
            configMapName,
            dockerImage,
            execution: safeEncode(this.execution),
            exId: this.execution.ex_id,
            exName: this.execution.k8sName,
            exUid: this.execution.k8sUid,
            jobId: this.execution.job_id,
            jobNameLabel,
            name,
            namespace: _.get(this.terasliceConfig, 'kubernetes_namespace', 'default'),
            nodeType: this.nodeType,
            replicas: this.execution.workers,
            shutdownTimeout: shutdownTimeoutSeconds
        };
        return config;
    }

    _makeTemplate(folder, fileName) {
        const filePath = path.join(__dirname, folder, `${fileName}.hbs`);
        const templateData = fs.readFileSync(filePath, 'utf-8');
        const templateKeys = ['{{', '}}'];

        return (config) => {
            const templated = barbe(templateData, templateKeys, config);
            return JSON.parse(templated);
        };
    }

    _setWorkerAntiAffinity() {
        if (this.terasliceConfig.kubernetes_worker_antiaffinity) {
            const targetKey = 'spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution';
            if (!_.has(this.resource, targetKey)) {
                _.set(this.resource, targetKey, []);
            }

            // eslint-disable-next-line max-len
            this.resource.spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution.push(
                {
                    weight: 1,
                    podAffinityTerm: {
                        labelSelector: {
                            matchExpressions: [
                                {
                                    key: 'app.kubernetes.io/name',
                                    operator: 'In',
                                    values: [
                                        'teraslice'
                                    ]
                                },
                                {
                                    key: 'app.kubernetes.io/instance',
                                    operator: 'In',
                                    values: [
                                        this.templateConfig.clusterNameLabel
                                    ]
                                }
                            ]
                        },
                        topologyKey: 'kubernetes.io/hostname'
                    }
                }
            );
        }
    }

    /**
     * Execution Controllers get tolerations and required affinities
     *
     * NOTE: We considered changing `execution_controller_targets` to be an
     * object but the inconsistency with `targets` made this awkward.  See the
     * `teraslice config with execution_controller_targets and job targets set`
     * test for an example.  If the syntax for this were to change, we should
     * also consider changing `execution.targets`, which is a change on the job.
     */
    _setExecutionControllerTargets() {
        if (this.terasliceConfig.execution_controller_targets) {
            _.forEach(this.terasliceConfig.execution_controller_targets, (target) => {
                this._setTargetRequired(target);
                this._setTargetAccepted(target);
            });
        }
    }

    _setEphemeralStorage() {
        if (this.execution.ephemeral_storage) {
            this.resource.spec.template.spec.containers[0].volumeMounts.push({
                name: 'ephemeral-volume',
                mountPath: '/ephemeral0'
            });
            this.resource.spec.template.spec.volumes.push({
                name: 'ephemeral-volume',
                emptyDir: {}
            });
        }
    }

    _setExternalPorts() {
        if (this.execution.external_ports) {
            _.forEach(this.execution.external_ports, (portValue) => {
                if (isNumber(portValue)) {
                    this.resource.spec.template.spec.containers[0].ports
                        .push({ containerPort: portValue });
                } else {
                    this.resource.spec.template.spec.containers[0].ports
                        .push(
                            {
                                name: portValue.name,
                                containerPort: portValue.port
                            }
                        );
                }
            });
        }
    }

    _setImagePullSecret() {
        if (this.terasliceConfig.kubernetes_image_pull_secret) {
            this.resource.spec.template.spec.imagePullSecrets = [
                { name: this.terasliceConfig.kubernetes_image_pull_secret }
            ];
        }
    }

    _setPriorityClassName() {
        if (this.terasliceConfig.kubernetes_priority_class_name) {
            if (this.nodeType === 'execution_controller'
                || (this.nodeType === 'worker' && this.execution.stateful)) {
                // eslint-disable-next-line max-len
                this.resource.spec.template.spec.priorityClassName = this.terasliceConfig.kubernetes_priority_class_name;
                // eslint-disable-next-line max-len
                this.resource.spec.template.metadata.labels[`${this.jobPropertyLabelPrefix}/stateful`] = 'true';
            }
        }
    }

    _setAssetsVolume() {
        if (this.terasliceConfig.assets_directory && this.terasliceConfig.assets_volume) {
            this.resource.spec.template.spec.volumes.push({
                name: this.terasliceConfig.assets_volume,
                persistentVolumeClaim: { claimName: this.terasliceConfig.assets_volume }
            });
            this.resource.spec.template.spec.containers[0].volumeMounts.push({
                name: this.terasliceConfig.assets_volume,
                mountPath: this.terasliceConfig.assets_directory
            });
        }
    }

    _setJobLabels() {
        if (this.execution.labels != null) {
            Object.entries(this.execution.labels).forEach(([k, v]) => {
                const key = `${this.jobLabelPrefix}/${_.replace(k, /[^a-zA-Z0-9\-._]/g, '-').substring(0, 63)}`;
                const value = _.replace(v, /[^a-zA-Z0-9\-._]/g, '-').substring(0, 63);
                this.resource.metadata.labels[key] = value;

                if (this.resource.kind !== 'Service') {
                    // Services don't have templates, so if it's a service,
                    // don't add this
                    this.resource.spec.template.metadata.labels[key] = value;
                }
            });
        }
    }

    _setVolumes() {
        if (this.execution.volumes != null) {
            _.forEach(this.execution.volumes, (volume) => {
                this.resource.spec.template.spec.volumes.push({
                    name: volume.name,
                    persistentVolumeClaim: { claimName: volume.name }
                });
                this.resource.spec.template.spec.containers[0].volumeMounts.push({
                    name: volume.name,
                    mountPath: volume.path
                });
            });
        }
    }

    _setResources() {
        let cpu;
        let memory;
        let maxMemory;

        const container = this.resource.spec.template.spec.containers[0];

        // use teraslice config as defaults and execution config will override it
        const envVars = Object.assign({}, this.terasliceConfig.env_vars, this.execution.env_vars);

        if (this.nodeType === 'worker') {
            if (this.execution.resources_requests_cpu
                || this.execution.resources_limits_cpu) {
                if (this.execution.resources_requests_cpu) {
                    _.set(container, 'resources.requests.cpu', this.execution.resources_requests_cpu);
                }
                if (this.execution.resources_limits_cpu) {
                    _.set(container, 'resources.limits.cpu', this.execution.resources_limits_cpu);
                }
            } else if (this.execution.cpu || this.terasliceConfig.cpu) {
                // The settings on the executions override the cluster configs
                cpu = this.execution.cpu || this.terasliceConfig.cpu || -1;
                _.set(container, 'resources.requests.cpu', cpu);
                _.set(container, 'resources.limits.cpu', cpu);
            }
            if (this.execution.resources_requests_memory
                || this.execution.resources_limits_memory) {
                _.set(container, 'resources.requests.memory', this.execution.resources_requests_memory);
                _.set(container, 'resources.limits.memory', this.execution.resources_limits_memory);
                maxMemory = this.execution.resources_limits_memory;
            } else if (this.execution.memory || this.terasliceConfig.memory) {
                // The settings on the executions override the cluster configs
                memory = this.execution.memory || this.terasliceConfig.memory || -1;
                _.set(container, 'resources.requests.memory', memory);
                _.set(container, 'resources.limits.memory', memory);
                maxMemory = memory;
            }
        }

        if (this.nodeType === 'execution_controller') {
            // The settings on the executions override the cluster configs
            cpu = this.execution.cpu_execution_controller
                || this.terasliceConfig.cpu_execution_controller || -1;
            memory = this.execution.memory_execution_controller
                || this.terasliceConfig.memory_execution_controller || -1;
            _.set(container, 'resources.requests.cpu', cpu);
            _.set(container, 'resources.limits.cpu', cpu);
            _.set(container, 'resources.requests.memory', memory);
            _.set(container, 'resources.limits.memory', memory);
            maxMemory = memory;
        }

        // NOTE: This sucks, this manages the memory env var but it ALSO is
        // responsible for doing the config and execution env var merge, which
        // should NOT be in this function
        setMaxOldSpaceViaEnv(container.env, envVars, maxMemory);
    }

    _setTargets() {
        if (_.has(this.execution, 'targets') && (!_.isEmpty(this.execution.targets))) {
            _.forEach(this.execution.targets, (target) => {
                // `required` is the default if no `constraint` is provided for
                // backwards compatibility and as the most likely case
                if (target.constraint === 'required' || !_.has(target, 'constraint')) {
                    this._setTargetRequired(target);
                }

                if (target.constraint === 'preferred') {
                    this._setTargetPreferred(target);
                }

                if (target.constraint === 'accepted') {
                    this._setTargetAccepted(target);
                }
            });
        }
    }

    _setTargetRequired(target) {
        const targetKey = 'spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution';
        if (!_.has(this.resource, targetKey)) {
            const nodeSelectorObj = {
                nodeSelectorTerms: [{ matchExpressions: [] }]
            };
            _.set(this.resource, targetKey, nodeSelectorObj);
        }

        this.resource.spec.template.spec.affinity.nodeAffinity
            .requiredDuringSchedulingIgnoredDuringExecution
            .nodeSelectorTerms[0].matchExpressions.push({
                key: target.key,
                operator: 'In',
                values: [target.value]
            });
    }

    _setTargetPreferred(target) {
        const targetKey = 'spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution';
        if (!_.has(this.resource, targetKey)) {
            _.set(this.resource, targetKey, []);
        }

        this.resource.spec.template.spec.affinity.nodeAffinity
            .preferredDuringSchedulingIgnoredDuringExecution.push({
                weight: 1,
                preference: {
                    matchExpressions: [{
                        key: target.key,
                        operator: 'In',
                        values: [target.value]
                    }]
                }
            });
    }

    _setTargetAccepted(target) {
        const targetKey = 'spec.template.spec.tolerations';
        if (!_.has(this.resource, targetKey)) {
            _.set(this.resource, targetKey, []);
        }

        this.resource.spec.template.spec.tolerations.push({
            key: target.key,
            operator: 'Equal',
            value: target.value,
            effect: 'NoSchedule'
        });
    }
}

module.exports = K8sResource;
<commit_msg>execution controllers stateful label change<commit_after>'use strict';

const fs = require('fs');
const path = require('path');

const barbe = require('barbe');
const _ = require('lodash');

const { isNumber } = require('@terascope/utils');

const { safeEncode } = require('../../../../../utils/encoding_utils');
const { setMaxOldSpaceViaEnv } = require('./utils');

class K8sResource {
    /**
     * K8sResource allows the generation of k8s resources based on templates.
     * After creating the object, the k8s resource is accessible on the objects
     * .resource property.
     *
     * @param {String} resourceType - jobs/services/deployments
     * @param {String} resourceName - worker/execution_controller
     * @param {Object} terasliceConfig - teraslice cluster config from context
     * @param {Object} execution - teraslice execution
     */
    constructor(resourceType, resourceName, terasliceConfig, execution) {
        this.execution = execution;
        this.jobLabelPrefix = 'job.teraslice.terascope.io';
        this.jobPropertyLabelPrefix = 'job-property.teraslice.terascope.io';
        this.nodeType = resourceName;
        this.terasliceConfig = terasliceConfig;

        if (resourceName === 'worker') {
            this.nameInfix = 'wkr';
        } else if (resourceName === 'execution_controller') {
            this.nameInfix = 'exc';
        } else {
            throw new Error(`Unsupported resourceName: ${resourceName}`);
        }

        this.templateGenerator = this._makeTemplate(resourceType, resourceName);
        this.templateConfig = this._makeConfig();
        this.resource = this.templateGenerator(this.templateConfig);

        this._setJobLabels();

        // Apply job `targets` setting as k8s nodeAffinity
        // We assume that multiple targets require both to match ...
        // NOTE: If you specify multiple `matchExpressions` associated with
        // `nodeSelectorTerms`, then the pod can be scheduled onto a node
        // only if *all* `matchExpressions` can be satisfied.
        this._setTargets();
        this._setResources();
        this._setVolumes();
        this._setAssetsVolume();
        this._setImagePullSecret();
        this._setEphemeralStorage();
        this._setExternalPorts();
        this._setPriorityClassName();

        if (resourceName === 'worker') {
            this._setWorkerAntiAffinity();
        }

        // Execution controller targets are required nodeAffinities, if
        // required job targets are also supplied, then *all* of the matches
        // will have to be satisfied for the job to be scheduled.  This also
        // adds tolerations for any specified targets
        if (resourceName === 'execution_controller') {
            this._setExecutionControllerTargets();
        }
    }

    _makeConfig() {
        const clusterName = _.get(this.terasliceConfig, 'name');
        const clusterNameLabel = clusterName.replace(/[^a-zA-Z0-9_\-.]/g, '_').substring(0, 63);
        const configMapName = _.get(
            this.terasliceConfig,
            'kubernetes_config_map_name',
            `${this.terasliceConfig.name}-worker`
        );
        const dockerImage = this.execution.kubernetes_image
            || this.terasliceConfig.kubernetes_image;
        // name needs to be a valid DNS name since it is used in the svc name,
        // so we can only permit alphanumeric and - characters.  _ is forbidden.
        // -> regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?'
        const jobNameLabel = this.execution.name
            .toLowerCase()
            .replace(/[^a-zA-Z0-9\-.]/g, '-')
            .replace(/^[^a-z]/, 'a')
            .replace(/[^a-z0-9]$/, '0')
            .substring(0, 63);
        const name = `ts-${this.nameInfix}-${jobNameLabel.substring(0, 35)}-${this.execution.job_id.substring(0, 13)}`;
        const shutdownTimeoutMs = _.get(this.terasliceConfig, 'shutdown_timeout', 60000);
        const shutdownTimeoutSeconds = Math.round(shutdownTimeoutMs / 1000);

        const config = {
            // assetsDirectory: _.get(this.terasliceConfig, 'assets_directory', ''),
            // assetsVolume: _.get(this.terasliceConfig, 'assets_volume', ''),
            clusterName,
            clusterNameLabel,
            configMapName,
            dockerImage,
            execution: safeEncode(this.execution),
            exId: this.execution.ex_id,
            exName: this.execution.k8sName,
            exUid: this.execution.k8sUid,
            jobId: this.execution.job_id,
            jobNameLabel,
            name,
            namespace: _.get(this.terasliceConfig, 'kubernetes_namespace', 'default'),
            nodeType: this.nodeType,
            replicas: this.execution.workers,
            shutdownTimeout: shutdownTimeoutSeconds
        };
        return config;
    }

    _makeTemplate(folder, fileName) {
        const filePath = path.join(__dirname, folder, `${fileName}.hbs`);
        const templateData = fs.readFileSync(filePath, 'utf-8');
        const templateKeys = ['{{', '}}'];

        return (config) => {
            const templated = barbe(templateData, templateKeys, config);
            return JSON.parse(templated);
        };
    }

    _setWorkerAntiAffinity() {
        if (this.terasliceConfig.kubernetes_worker_antiaffinity) {
            const targetKey = 'spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution';
            if (!_.has(this.resource, targetKey)) {
                _.set(this.resource, targetKey, []);
            }

            // eslint-disable-next-line max-len
            this.resource.spec.template.spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution.push(
                {
                    weight: 1,
                    podAffinityTerm: {
                        labelSelector: {
                            matchExpressions: [
                                {
                                    key: 'app.kubernetes.io/name',
                                    operator: 'In',
                                    values: [
                                        'teraslice'
                                    ]
                                },
                                {
                                    key: 'app.kubernetes.io/instance',
                                    operator: 'In',
                                    values: [
                                        this.templateConfig.clusterNameLabel
                                    ]
                                }
                            ]
                        },
                        topologyKey: 'kubernetes.io/hostname'
                    }
                }
            );
        }
    }

    /**
     * Execution Controllers get tolerations and required affinities
     *
     * NOTE: We considered changing `execution_controller_targets` to be an
     * object but the inconsistency with `targets` made this awkward.  See the
     * `teraslice config with execution_controller_targets and job targets set`
     * test for an example.  If the syntax for this were to change, we should
     * also consider changing `execution.targets`, which is a change on the job.
     */
    _setExecutionControllerTargets() {
        if (this.terasliceConfig.execution_controller_targets) {
            _.forEach(this.terasliceConfig.execution_controller_targets, (target) => {
                this._setTargetRequired(target);
                this._setTargetAccepted(target);
            });
        }
    }

    _setEphemeralStorage() {
        if (this.execution.ephemeral_storage) {
            this.resource.spec.template.spec.containers[0].volumeMounts.push({
                name: 'ephemeral-volume',
                mountPath: '/ephemeral0'
            });
            this.resource.spec.template.spec.volumes.push({
                name: 'ephemeral-volume',
                emptyDir: {}
            });
        }
    }

    _setExternalPorts() {
        if (this.execution.external_ports) {
            _.forEach(this.execution.external_ports, (portValue) => {
                if (isNumber(portValue)) {
                    this.resource.spec.template.spec.containers[0].ports
                        .push({ containerPort: portValue });
                } else {
                    this.resource.spec.template.spec.containers[0].ports
                        .push(
                            {
                                name: portValue.name,
                                containerPort: portValue.port
                            }
                        );
                }
            });
        }
    }

    _setImagePullSecret() {
        if (this.terasliceConfig.kubernetes_image_pull_secret) {
            this.resource.spec.template.spec.imagePullSecrets = [
                { name: this.terasliceConfig.kubernetes_image_pull_secret }
            ];
        }
    }

    _setPriorityClassName() {
        if (this.terasliceConfig.kubernetes_priority_class_name) {
            if (this.nodeType === 'execution_controller') {
                // eslint-disable-next-line max-len
                this.resource.spec.template.spec.priorityClassName = this.terasliceConfig.kubernetes_priority_class_name;
                if (this.execution.stateful) {
                    // eslint-disable-next-line max-len
                    this.resource.spec.template.metadata.labels[`${this.jobPropertyLabelPrefix}/stateful`] = 'true';
                }
            }
            if (this.nodeType === 'worker' && this.execution.stateful) {
                // eslint-disable-next-line max-len
                this.resource.spec.template.spec.priorityClassName = this.terasliceConfig.kubernetes_priority_class_name;
                // eslint-disable-next-line max-len
                this.resource.spec.template.metadata.labels[`${this.jobPropertyLabelPrefix}/stateful`] = 'true';
            }
        }
    }

    _setAssetsVolume() {
        if (this.terasliceConfig.assets_directory && this.terasliceConfig.assets_volume) {
            this.resource.spec.template.spec.volumes.push({
                name: this.terasliceConfig.assets_volume,
                persistentVolumeClaim: { claimName: this.terasliceConfig.assets_volume }
            });
            this.resource.spec.template.spec.containers[0].volumeMounts.push({
                name: this.terasliceConfig.assets_volume,
                mountPath: this.terasliceConfig.assets_directory
            });
        }
    }

    _setJobLabels() {
        if (this.execution.labels != null) {
            Object.entries(this.execution.labels).forEach(([k, v]) => {
                const key = `${this.jobLabelPrefix}/${_.replace(k, /[^a-zA-Z0-9\-._]/g, '-').substring(0, 63)}`;
                const value = _.replace(v, /[^a-zA-Z0-9\-._]/g, '-').substring(0, 63);
                this.resource.metadata.labels[key] = value;

                if (this.resource.kind !== 'Service') {
                    // Services don't have templates, so if it's a service,
                    // don't add this
                    this.resource.spec.template.metadata.labels[key] = value;
                }
            });
        }
    }

    _setVolumes() {
        if (this.execution.volumes != null) {
            _.forEach(this.execution.volumes, (volume) => {
                this.resource.spec.template.spec.volumes.push({
                    name: volume.name,
                    persistentVolumeClaim: { claimName: volume.name }
                });
                this.resource.spec.template.spec.containers[0].volumeMounts.push({
                    name: volume.name,
                    mountPath: volume.path
                });
            });
        }
    }

    _setResources() {
        let cpu;
        let memory;
        let maxMemory;

        const container = this.resource.spec.template.spec.containers[0];

        // use teraslice config as defaults and execution config will override it
        const envVars = Object.assign({}, this.terasliceConfig.env_vars, this.execution.env_vars);

        if (this.nodeType === 'worker') {
            if (this.execution.resources_requests_cpu
                || this.execution.resources_limits_cpu) {
                if (this.execution.resources_requests_cpu) {
                    _.set(container, 'resources.requests.cpu', this.execution.resources_requests_cpu);
                }
                if (this.execution.resources_limits_cpu) {
                    _.set(container, 'resources.limits.cpu', this.execution.resources_limits_cpu);
                }
            } else if (this.execution.cpu || this.terasliceConfig.cpu) {
                // The settings on the executions override the cluster configs
                cpu = this.execution.cpu || this.terasliceConfig.cpu || -1;
                _.set(container, 'resources.requests.cpu', cpu);
                _.set(container, 'resources.limits.cpu', cpu);
            }
            if (this.execution.resources_requests_memory
                || this.execution.resources_limits_memory) {
                _.set(container, 'resources.requests.memory', this.execution.resources_requests_memory);
                _.set(container, 'resources.limits.memory', this.execution.resources_limits_memory);
                maxMemory = this.execution.resources_limits_memory;
            } else if (this.execution.memory || this.terasliceConfig.memory) {
                // The settings on the executions override the cluster configs
                memory = this.execution.memory || this.terasliceConfig.memory || -1;
                _.set(container, 'resources.requests.memory', memory);
                _.set(container, 'resources.limits.memory', memory);
                maxMemory = memory;
            }
        }

        if (this.nodeType === 'execution_controller') {
            // The settings on the executions override the cluster configs
            cpu = this.execution.cpu_execution_controller
                || this.terasliceConfig.cpu_execution_controller || -1;
            memory = this.execution.memory_execution_controller
                || this.terasliceConfig.memory_execution_controller || -1;
            _.set(container, 'resources.requests.cpu', cpu);
            _.set(container, 'resources.limits.cpu', cpu);
            _.set(container, 'resources.requests.memory', memory);
            _.set(container, 'resources.limits.memory', memory);
            maxMemory = memory;
        }

        // NOTE: This sucks, this manages the memory env var but it ALSO is
        // responsible for doing the config and execution env var merge, which
        // should NOT be in this function
        setMaxOldSpaceViaEnv(container.env, envVars, maxMemory);
    }

    _setTargets() {
        if (_.has(this.execution, 'targets') && (!_.isEmpty(this.execution.targets))) {
            _.forEach(this.execution.targets, (target) => {
                // `required` is the default if no `constraint` is provided for
                // backwards compatibility and as the most likely case
                if (target.constraint === 'required' || !_.has(target, 'constraint')) {
                    this._setTargetRequired(target);
                }

                if (target.constraint === 'preferred') {
                    this._setTargetPreferred(target);
                }

                if (target.constraint === 'accepted') {
                    this._setTargetAccepted(target);
                }
            });
        }
    }

    _setTargetRequired(target) {
        const targetKey = 'spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution';
        if (!_.has(this.resource, targetKey)) {
            const nodeSelectorObj = {
                nodeSelectorTerms: [{ matchExpressions: [] }]
            };
            _.set(this.resource, targetKey, nodeSelectorObj);
        }

        this.resource.spec.template.spec.affinity.nodeAffinity
            .requiredDuringSchedulingIgnoredDuringExecution
            .nodeSelectorTerms[0].matchExpressions.push({
                key: target.key,
                operator: 'In',
                values: [target.value]
            });
    }

    _setTargetPreferred(target) {
        const targetKey = 'spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution';
        if (!_.has(this.resource, targetKey)) {
            _.set(this.resource, targetKey, []);
        }

        this.resource.spec.template.spec.affinity.nodeAffinity
            .preferredDuringSchedulingIgnoredDuringExecution.push({
                weight: 1,
                preference: {
                    matchExpressions: [{
                        key: target.key,
                        operator: 'In',
                        values: [target.value]
                    }]
                }
            });
    }

    _setTargetAccepted(target) {
        const targetKey = 'spec.template.spec.tolerations';
        if (!_.has(this.resource, targetKey)) {
            _.set(this.resource, targetKey, []);
        }

        this.resource.spec.template.spec.tolerations.push({
            key: target.key,
            operator: 'Equal',
            value: target.value,
            effect: 'NoSchedule'
        });
    }
}

module.exports = K8sResource;
<|endoftext|>


<commit_before>// @flow

import React, { Component } from 'react';
import type { ElementProps, ComponentType } from 'react';

export type LabelProps = {|
    labelAttributes: {
        role: 'tooltip',
        tabIndex: '0',
        'aria-describedby': string,
        onFocus: () => void,
    },
    isHidden: boolean,
    requestHide: () => void,
    requestShow: () => void,
    requestToggle: () => void,
|};

export type OverlayProps = {|
    overlayAttributes: {
        tabIndex: '-1',
        id: string,
        'aria-hidden': boolean,
    },
    isHidden: boolean,
    requestHide: () => void,
    requestShow: () => void,
    requestToggle: () => void,
|};

export type TooltipState = {
    isHidden: boolean,
};

export type TooltipProps = ElementProps<'div'> & {
    label: ComponentType<LabelProps>,
    overlay: ComponentType<OverlayProps>,
    containerRef?: HTMLDivElement => void,
};

let counter = 0;

class Tooltip extends Component<TooltipProps, TooltipState> {
    constructor(props: TooltipProps) {
        super(props);
        this.identifier = `react-accessible-tooltip-${counter}`;
        counter += 1;
    }

    state = {
        isHidden: true,
    };

    onBlur({ relatedTarget, currentTarget }: SyntheticFocusEvent<HTMLElement>) {
        // relatedTarget is better for React testability etc, but activeElement works as an IE11 fallback:
        const newTarget = relatedTarget || document.activeElement;

        // The idea of this logic is that we should only close the tooltip if focus has shifted from the tooltip AND all of its descendents.
        if (!(newTarget && newTarget instanceof HTMLElement)) {
            this.hide();
        } else if (!currentTarget.contains(newTarget)) {
            this.hide();
        }
    }

    hide = (): void => {
        this.setState({ isHidden: true });
    };

    show = (): void => {
        this.setState({ isHidden: false });
    };

    toggle = (): void => {
        this.setState({ isHidden: !this.state.isHidden });
    };

    identifier: string;

    render() {
        const {
            label: Label,
            overlay: Overlay,
            containerRef,
            ...rest
        } = this.props;

        const { isHidden } = this.state;

        const labelProps: LabelProps = {
            labelAttributes: {
                role: 'tooltip',
                tabIndex: '0',
                'aria-describedby': `#${this.identifier}`,
                onFocus: this.show,
            },
            isHidden,
            requestHide: this.hide,
            requestShow: this.show,
            requestToggle: this.toggle,
        };

        const overlayProps: OverlayProps = {
            overlayAttributes: {
                tabIndex: '-1',
                id: this.identifier,
                'aria-hidden': this.state.isHidden,
            },
            isHidden,
            requestHide: this.hide,
            requestShow: this.show,
            requestToggle: this.toggle,
        };

        return (
            <div
                {...rest}
                onBlur={e => this.onBlur(e)}
                ref={ref => {
                    containerRef && containerRef(ref);
                }}
            >
                <Label {...labelProps} />
                <Overlay {...overlayProps} />
            </div>
        );
    }
}

export default Tooltip;
<commit_msg>Fix linting error<commit_after>// @flow

import React, { Component } from 'react';
import type { ElementProps, ComponentType } from 'react';

export type LabelProps = {|
    labelAttributes: {
        role: 'tooltip',
        tabIndex: '0',
        'aria-describedby': string,
        onFocus: () => void,
    },
    isHidden: boolean,
    requestHide: () => void,
    requestShow: () => void,
    requestToggle: () => void,
|};

export type OverlayProps = {|
    overlayAttributes: {
        tabIndex: '-1',
        id: string,
        'aria-hidden': boolean,
    },
    isHidden: boolean,
    requestHide: () => void,
    requestShow: () => void,
    requestToggle: () => void,
|};

export type TooltipState = {
    isHidden: boolean,
};

export type TooltipProps = ElementProps<'div'> & {
    label: ComponentType<LabelProps>,
    overlay: ComponentType<OverlayProps>,
    containerRef?: HTMLDivElement => void,
};

let counter = 0;

class Tooltip extends Component<TooltipProps, TooltipState> {
    constructor(props: TooltipProps) {
        super(props);
        this.identifier = `react-accessible-tooltip-${counter}`;
        counter += 1;
    }

    state = {
        isHidden: true,
    };

    onBlur({ relatedTarget, currentTarget }: SyntheticFocusEvent<HTMLElement>) {
        // relatedTarget is better for React testability etc, but activeElement works as an IE11 fallback:
        const newTarget = relatedTarget || document.activeElement;

        // The idea of this logic is that we should only close the tooltip if focus has shifted from the tooltip AND all of its descendents.
        if (!(newTarget && newTarget instanceof HTMLElement)) {
            this.hide();
        } else if (!currentTarget.contains(newTarget)) {
            this.hide();
        }
    }

    hide = (): void => {
        this.setState({ isHidden: true });
    };

    show = (): void => {
        this.setState({ isHidden: false });
    };

    toggle = (): void => {
        this.setState({ isHidden: !this.state.isHidden });
    };

    identifier: string;

    render() {
        const {
            label: Label,
            overlay: Overlay,
            containerRef,
            ...rest
        } = this.props;

        const { isHidden } = this.state;

        const labelProps: LabelProps = {
            labelAttributes: {
                role: 'tooltip',
                tabIndex: '0',
                'aria-describedby': `#${this.identifier}`,
                onFocus: this.show,
            },
            isHidden,
            requestHide: this.hide,
            requestShow: this.show,
            requestToggle: this.toggle,
        };

        const overlayProps: OverlayProps = {
            overlayAttributes: {
                tabIndex: '-1',
                id: this.identifier,
                'aria-hidden': this.state.isHidden,
            },
            isHidden,
            requestHide: this.hide,
            requestShow: this.show,
            requestToggle: this.toggle,
        };

        return (
            <div
                {...rest}
                onBlur={e => this.onBlur(e)}
                ref={ref => {
                    if (containerRef) {
                        containerRef(ref);
                    }
                }}
            >
                <Label {...labelProps} />
                <Overlay {...overlayProps} />
            </div>
        );
    }
}

export default Tooltip;
<|endoftext|>


<commit_before>/*jslint browser: true*/
/*global jQuery */
(function ($) {
    "use strict";

    var InspectR = {
        start: function (config) {
            config = $.extend({}, InspectR.defaults, config);

            var viewModel = new InspectRViewModel(config);
            ko.applyBindings(viewModel, config.rootNode);

            var Router = Backbone.Router.extend({
                routes: {
                    "session/:id": "loadSession",
                    "*uniquekey": "start"
                },
                start: viewModel.start,
                loadSession: viewModel.loadSession
            });

            var router = new Router;

            $.connection.hub.start({
                // transport: 'auto'
            }, function () {
                Backbone.history.start({});
            });
        },
        defaults: {
            // inspectorKey: ''
            // rootNode:'#inspectr'
        }
    };

    var InspectRViewModel = function (config) {
        var self = this;

        self.inspectorKey = config.inspector;

        // router.on('route:loadSession', self.loadSession);
        // router.on('route:start', self.start);

        self.hub = $.connection.inspectRHub;
        var client = self.hub.client;
        var server = self.hub.server;

        self.NewTitle = ko.observable();
        self.IsEditingTitle = ko.observable(false);
        self.UserProfile = ko.observable();
        self.Requests = ko.mapping.fromJS([]);
        self.Inspector = ko.observable();
        self.SupportedContentTypes = _.keys(CodeMirror.mimeModes).sort();

        self.RequestList = ko.computed(function () {
            return self.Requests();
        });

        self.start = function (uniquekey) {
            if (uniquekey && uniquekey.length > 0) {
                self.inspectorKey = uniquekey;
            }

            self.updateUserProfile();

            self.startInspect();
        };

        self.startInspect = function () {
            // self.Inspector(null); // disable this to not flicker the screen
            self.Requests([]);
            server.startInspect(self.inspectorKey)
                .done(function (result) {
                    if (result) {
                        self.Inspector(ko.mapping.fromJS(result));
                        self.loadRecentRequests();
                    }
                });
        };

        self.loadRecentRequests = function () {
            server.getRecentRequests(self.inspectorKey)
                .done(function (result) {
                    if (result && result.length > 0) {
                        ko.mapping.fromJS(result, self.Requests);
                    }
                });
        };

        self.removeInspector = function (inspector) {
            if (self.Inspector().Id() == inspector.Id()) {
                throw "Cannot remove current inspector";
            }

            if (!confirm('Remove inspector: ' + inspector.UniqueKey())) {
                return;
            }

            server.removeInspectorFromUserProfile(inspector.Id())
                .done(function () {
                    self.UserProfile().Inspectors.remove(inspector);
                });
        };

        self.loadSession = function () {

        };

        self.saveTitle = function () {
            server.setTitle(self.Inspector().Id(), self.NewTitle())
                .done(function () {
                    self.IsEditingTitle(false);
                    self.Inspector().Title(self.NewTitle());
                    self.updateUserProfile();
                });
        };

        self.updateUserProfile = function () {
            server.getUserProfile()
                .done(function (result) {
                    if (result) {
                        self.UserProfile(ko.mapping.fromJS(result));
                    }
                });
        };

        self.clearRecentRequests = function () {
            server.clearRecentRequests(self.inspectorKey);
            self.Requests.removeAll();
        };

        self.P = function (property, data) {
            if (!ko.isObservable(data[property])) {
                data[property] = ko.observable();
            }
            return data[property];
        };

        client.requestLogged = function (inspector, request) {
            // console.log('http request');
            // console.log(inspector, request);
            self.Requests.unshift(request);
        };
    };

    $.inspectR = InspectR;
}(jQuery));
<commit_msg>force longPolling for now<commit_after>/*jslint browser: true*/
/*global jQuery */
(function ($) {
    "use strict";

    var InspectR = {
        start: function (config) {
            config = $.extend({}, InspectR.defaults, config);

            var viewModel = new InspectRViewModel(config);
            ko.applyBindings(viewModel, config.rootNode);

            var Router = Backbone.Router.extend({
                routes: {
                    "session/:id": "loadSession",
                    "*uniquekey": "start"
                },
                start: viewModel.start,
                loadSession: viewModel.loadSession
            });

            var router = new Router;

            $.connection.hub.start({
                transport: 'longPolling'
            }, function () {
                Backbone.history.start({});
            });
        },
        defaults: {
            // inspectorKey: ''
            // rootNode:'#inspectr'
        }
    };

    var InspectRViewModel = function (config) {
        var self = this;

        self.inspectorKey = config.inspector;

        // router.on('route:loadSession', self.loadSession);
        // router.on('route:start', self.start);

        self.hub = $.connection.inspectRHub;
        var client = self.hub.client;
        var server = self.hub.server;

        self.NewTitle = ko.observable();
        self.IsEditingTitle = ko.observable(false);
        self.UserProfile = ko.observable();
        self.Requests = ko.mapping.fromJS([]);
        self.Inspector = ko.observable();
        self.SupportedContentTypes = _.keys(CodeMirror.mimeModes).sort();

        self.RequestList = ko.computed(function () {
            return self.Requests();
        });

        self.start = function (uniquekey) {
            if (uniquekey && uniquekey.length > 0) {
                self.inspectorKey = uniquekey;
            }

            self.updateUserProfile();

            self.startInspect();
        };

        self.startInspect = function () {
            // self.Inspector(null); // disable this to not flicker the screen
            self.Requests([]);
            server.startInspect(self.inspectorKey)
                .done(function (result) {
                    if (result) {
                        self.Inspector(ko.mapping.fromJS(result));
                        self.loadRecentRequests();
                    }
                });
        };

        self.loadRecentRequests = function () {
            server.getRecentRequests(self.inspectorKey)
                .done(function (result) {
                    if (result && result.length > 0) {
                        ko.mapping.fromJS(result, self.Requests);
                    }
                });
        };

        self.removeInspector = function (inspector) {
            if (self.Inspector().Id() == inspector.Id()) {
                throw "Cannot remove current inspector";
            }

            if (!confirm('Remove inspector: ' + inspector.UniqueKey())) {
                return;
            }

            server.removeInspectorFromUserProfile(inspector.Id())
                .done(function () {
                    self.UserProfile().Inspectors.remove(inspector);
                });
        };

        self.loadSession = function () {

        };

        self.saveTitle = function () {
            server.setTitle(self.Inspector().Id(), self.NewTitle())
                .done(function () {
                    self.IsEditingTitle(false);
                    self.Inspector().Title(self.NewTitle());
                    self.updateUserProfile();
                });
        };

        self.updateUserProfile = function () {
            server.getUserProfile()
                .done(function (result) {
                    if (result) {
                        self.UserProfile(ko.mapping.fromJS(result));
                    }
                });
        };

        self.clearRecentRequests = function () {
            server.clearRecentRequests(self.inspectorKey);
            self.Requests.removeAll();
        };

        self.P = function (property, data) {
            if (!ko.isObservable(data[property])) {
                data[property] = ko.observable();
            }
            return data[property];
        };

        client.requestLogged = function (inspector, request) {
            // console.log('http request');
            // console.log(inspector, request);
            self.Requests.unshift(request);
        };
    };

    $.inspectR = InspectR;
}(jQuery));
<|endoftext|>


<commit_before>//视图配置功能
app.directive('uisqlview', function($http,$log,$ocLazyLoad,commonService,$uibModal) {

	/*新增初始化*/
	var init = function(data){
		if(data){
			return data;
		}
		return {
			multiple :0,
			showRowNum:0,
			name:"",
			code:"",
			remark:"",
			sqlId:"",
			fieldList : [],
			columnList : [],
			conditions : "[]",
			buttons : "[]"
		};
	};

	//子窗口
	var modalDialog = function($scope,$http,$uibModal,$log,$uibModalInstance,param) { //接收子页传值
		$scope.optionData = OPTION_WHETHER;

		//基本信息
		$scope.data = init(param.row);
		//修改字段
		$scope.fieldList = param.sqlView.fieldSetting;
		//TODO SQL字段
		$scope.columnList = $scope.data.columnList;
		//TODO 过滤条件
		$scope.filterList = eval($scope.data.conditions);
		//TODO 按钮设置
		$scope.buttonList = eval($scope.data.buttons);

		//保存操作
		$scope.save = function() {
			$scope.data.columnList = $scope.columnList;
			$scope.data.filterList = $scope.filterList;
			$scope.data.buttonList = $scope.buttonList;
			$scope.data.treeData = $scope.treeData;
			saveOfClose($http,param.sqlView.controller + "/save",$scope.data,$uibModalInstance);
		};
		$scope.cancel = function() {
			$uibModalInstance.dismiss('cancel');
		};
		/**=======================过滤设置================================================**/
		$scope.ctype = DICT_COMPONENTTYPE;
		$scope.expr = DICT_EXPRESSION;
		$scope.isType = function(type,ctype){
			return _.isEqual(type, ctype);
		}
		//选中条件
		$scope.addFilterItem = function(item){
			var opt = {};
			opt.title=item.title
			opt.field = item.field;
			opt.dataType = item.dataType;
			opt.componentType=DICT_COMPONENTTYPE[0].value;
			opt.expr = DICT_EXPRESSION[0].value;
			opt.isSort = item.isSort;
			opt.expand = item.expand;
			$scope.filterList.push(opt);
		}
		//移除选项
		$scope.removeItem=function(item){
			$scope.filterList.splice(_.findIndex($scope.filterList, item),1);
		}
		/**=======================生成列表================================================**/
		//验证规则
		$scope.checkRule = function(item){
			commonService.show({template:'
<h8 class="modal-title">验证规则

'+
				'
'+
				'<input type="checkbox" id="{{regx}}"  ng-click="checked(regx)" ng-checked="isChecked(regx)" ></input>'+
				'{{REGULAR_EXPRESSION[regx].tip}}

',
				/**'
<button type="button" class="btn btn-default waves-effect" data-dismiss="modal"  ng-click="cancel()">取消</button>'+
				'<button type="button" class="btn btn-info waves-effect waves-light" ng-click="ok()">确定</button>
'**/
				controller:function($scope,$http,$uibModal,$log,$uibModalInstance,param) {
					$scope.REGULAR_EXPRESSION = REGULAR_EXPRESSION;
					$scope.keys = _.keys(REGULAR_EXPRESSION);
					var rule = eval(item.rule);
					$scope.isChecked = function(regx){
						return rule.indexOf(regx) > -1;
					}
					$scope.checked = function(regx){
						updateCheckBox(rule,regx);
						item.rule = JSON.stringify(rule);
					}
				},
				param:{item:item},
				callback:function(){

				}});

		}

		//生成列表
		$scope.createFieldData = function(){
			$.confirm({
				confirm: function(){
					commonService.ajax({url:"/search/sqlview/createField",data:JSON.stringify({sqlId:$scope.data.sqlId}),success:function(data){
						if(STATUS_CODE.SUCCESS == data.code){
							$scope.columnList = data.result;

							//消化
							$scope.$digest();
						}else{
							$.error(data.message);
						}
					}});
				}
			});
		}

		//移除选项
		$scope.removeField=function(item){
			$scope.columnList.splice(_.findIndex($scope.columnList, item),1);
		}

		//是否显示文本编辑器
		$scope.turnon = function(item){
			return item.componentType == 'VIEWSELECTOR';
		}

		//参数设置弹出框
		$scope.sqlSelectParamCtrl = function($scope,$http,$uibModal,$log,$uibModalInstance,param){

			//视图所有列
			$scope.columnList =  param.param;
			if(param.data.expand){
				$scope.expand = JSON.parse(param.data.expand);
			}else{
				$scope.expand={
					code:"",
					mappingList:[{
						valueKey:param.data.field, //数据键值
						textValue:"",//对应返回文本值
						icon:"md-add"
					}]
				};
			}

			$scope.adRecord = function(idx){

				//最后一条增加数据
				if($scope.expand.mappingList.length - 1 == idx){

					//往第一行插入数据
					$scope.expand.mappingList.splice(0,0,{
							valueKey:"",
							textValue:"",
							icon:"md-remove"});
				}else{

					//减去
					$scope.expand.mappingList.splice(idx,1);
				}
			}

			//选择器ok按钮
			$scope.ok = function() {
				param.data.expand = JSON.stringify($scope.expand);
				$uibModalInstance.close($scope.expand);
			};

			//取消
			$scope.cancel = function() {
				$uibModalInstance.dismiss('cancel');
			};
		}

		/**=======================按钮设置=======================================================================================================================**/
		$scope.btype = OPTION_BUTTON;
		$scope.winSize=WIN_SIZE;

		//添加按钮
		$scope.createButton = function(){
			var item = {};
			item.id = $.uuid();
			item.title="";
			item.icon="md-add";
			item.type= 0;
			item.lazyurl = "";
			item.url = "";
			item.ctrl = "";
			item.showWin= 0;
			item.winSize= 40;
			item.idx = $scope.buttonList.length + 1;
			$scope.buttonList.push(item);
		}
		$scope.removeButton = function(item){
			$scope.buttonList.splice(item,1);
		}

		//增、删、改、查
		$scope.insert = {id:CRUD_CODE.INSERT,title:"增加",icon:"md-add",type:1,url:"",showWin:1,winSize:50};
		$scope.update = {id:CRUD_CODE.UPDATE,title:"修改",icon:"ion-edit",type:0,url:"",showWin:1,winSize:50};
		$scope.remove = {id:CRUD_CODE.DELETE,title:"删除",icon:"ion-trash-a",type:0,url:"",showWin:0,winSize:""};
		$scope.view   = {id:CRUD_CODE.VIEW,title:"查看",icon:"ion-eye",type:0,url:"",showWin:1,winSize:50};

		$scope.crudCheck = function(item){
			var idx = getArrayIdxById($scope.buttonList,item);
			if(idx > -1){
				$scope.buttonList.splice(idx,1);
			}else{
				$scope.buttonList.push(item);
			}
		}
		$scope.isChecked = function(item){
			return getArrayIdxById($scope.buttonList,item) > -1;
		}

		//图标选择控制器
		var checkedIcon = function($scope,$http,$uibModal,$log,$uibModalInstance,param){
			$scope.ok=function($event){
				param.icon = $($event.target).attr("class");

				//关闭图标
				$uibModalInstance.close();
			}
		}

		//选择图标
		$scope.checkedIcon = function(item){
			var options = {templateUrl:"/templates/search/sqlview/iconSelector.html",controller:checkedIcon,
					param:item,
					size:80};
			commonService.show(options);
		}
		/**=======================树设置====================================**/
		$scope.nodeOpts = TREE_OPTIONS;
		//功能树形
		$scope.treeData = {
			url:"", //默认树形统一接口
			idKey:"id",
			name:"name",
			pIdKey: "parentId",
			rootPId: 0,
			isShow:0,
			nodeOpts:'ALL',
			width:2,
			sqlId:'',
			relationField:''
		};
		if($scope.data.treeData){
			$scope.treeData =  JSON.parse($scope.data.treeData);
		}

		//初始化
		$scope.verifyRule = $scope.treeData.isShow == 1 ? "[10001]" : "[]";

		//校验必填
		$scope.treeRequired = function(){
			$scope.verifyRule = $scope.treeData.isShow == 0 ? "[10001]" : "[]";
		}

	}

	//列表记录弹窗
	function baseIndexEdit(commonService,dataUrl,templateUrl,ctrl,param,callback,size) {

		if(param.id){
			/*根据选中ID获取最新数据*/
			commonService.post(dataUrl,param,function(data){

				if(data.code == STATUS_CODE.SUCCESS){
					param.row = data.result;
					commonService.show({templateUrl:templateUrl,controller:ctrl,param:param,callback:callback,size:(size||40)});
				}else{
					$.error(data.message);
				}
			});

		}else{
			commonService.show({templateUrl:templateUrl,controller:ctrl,param:param,callback:callback,size:(size||40)});
		}
	};

	return {
		restrict:'E',
		templateUrl:"templates/basic/directive/index.html",
		replace : false,
		transclude : false,
		scope:{
			url:"@",
			param:"=",
			returndata:"="
		},
		compile: function compile(tElement, tAttrs, transclude) {
			return {
				pre: function preLink(scope, iElement, iAttrs, controller) {

					//设置树参数
					function initTree(treeConfig){

						if(_.isUndefined(treeConfig)){
							return;
						}

						return {
							setting:{
								async:{
									url:treeConfig.url,
									type:"post",
									contentType: "application/json",
									enable:true
								},
								data:{
									simpleData:{
										enable: true, //不需要用户再把数据库中取出的 List 强行转换为复杂的 JSON 嵌套格式
										idKey: treeConfig.idKey,
										pIdKey: treeConfig.pIdKey,
										rootPId: treeConfig.rootPId
									}
								},
								callback: {
									onClick: function(event,treeId,node,idx){
										scope.parameter.treeNode=node;
										scope.$broadcast(scope.grid.id);
									}
								}
							}
						};
					}

					//工具栏
					scope.toolbar = {id:$.uuid()};

					//请求参数
					scope.parameter = $.extend({id:$.uuid()},scope.param);
					function success(data){
						data = eval('(' + data + ')');
						scope.sqlView = data;
						scope.grid = {
								id:$.uuid(),
								dataList:{},
								search:function(){
									scope.$broadcast(scope.grid.id);
								},
								url:data.controller + '/list',
								fieldData:data.fieldData
						};
						scope.treeconfig = initTree(data.treeData);

						//查询参数
						scope.parameter = {
								condition:scope.sqlView.filterData
						};
					}
					commonService.ajax({url:scope.url,success:success,type:"get",dataType:"text",async:false});

					//选中列表
					var checkedData= [];
					scope.rowClick = function(item,option){
						scope.returndata.option = option;

						//单选
						if(_.isEqual(GRID_OPTIONS.SINGLE, option)){
							checkedData[0] = item;
						}else{
							updateCheckBox(checkedData,item);
						}
						scope.returndata.data = checkedData;
					}

					scope.crud = function crud(item,func){
						switch(func.id){
						case CRUD_CODE.INSERT:
						case CRUD_CODE.UPDATE:
							item = item || {id:""};
							baseIndexEdit(commonService,scope.sqlView.controller + '/findById',func.url,modalDialog,{id:item.id,sqlView:scope.sqlView},scope.grid.search,80);
							break;
						case CRUD_CODE.DELETE://删除
							remove(commonService,scope.sqlView.controller + '/delete',{id:item.id},scope.search);
							break;

						case CRUD_CODE.VIEW: //查
							break;
						}
					}
					//判断是否显示树
					scope.showtree = function(){
						return !_.isUndefined(scope.treeconfig);
					}
				}
			}
		}
	};
});<commit_msg>代码格式优化<commit_after>//视图配置功能
app.directive('uisqlview', function($http,$log,$ocLazyLoad,commonService,$uibModal) {

	/*新增初始化*/
	var init = function(data){
		if(data){
			return data;
		}
		return {
			multiple :0,
			showRowNum:0,
			name:"",
			code:"",
			remark:"",
			sqlId:"",
			fieldList : [],
			columnList : [],
			conditions : "[]",
			buttons : "[]"
		};
	};

	//子窗口
	var modalDialog = function($scope,$http,$uibModal,$log,$uibModalInstance,param) { //接收子页传值
		$scope.optionData = OPTION_WHETHER;

		//基本信息
		$scope.data = init(param.row);
		//修改字段
		$scope.fieldList = param.sqlView.fieldSetting;
		//TODO SQL字段
		$scope.columnList = $scope.data.columnList;
		//TODO 过滤条件
		$scope.filterList = eval($scope.data.conditions);
		//TODO 按钮设置
		$scope.buttonList = eval($scope.data.buttons);

		//保存操作
		$scope.save = function() {
			$scope.data.columnList = $scope.columnList;
			$scope.data.filterList = $scope.filterList;
			$scope.data.buttonList = $scope.buttonList;
			$scope.data.treeData = $scope.treeData;
			saveOfClose($http,param.sqlView.controller + "/save",$scope.data,$uibModalInstance);
		};
		$scope.cancel = function() {
			$uibModalInstance.dismiss('cancel');
		};
		/**=======================过滤设置================================================**/
		$scope.ctype = DICT_COMPONENTTYPE;
		$scope.expr = DICT_EXPRESSION;
		$scope.isType = function(type,ctype){
			return _.isEqual(type, ctype);
		}
		//选中条件
		$scope.addFilterItem = function(item){
			var opt = {};
			opt.title=item.title
			opt.field = item.field;
			opt.dataType = item.dataType;
			opt.componentType=DICT_COMPONENTTYPE[0].value;
			opt.expr = DICT_EXPRESSION[0].value;
			opt.isSort = item.isSort;
			opt.expand = item.expand;
			$scope.filterList.push(opt);
		}
		//移除选项
		$scope.removeItem=function(item){
			$scope.filterList.splice(_.findIndex($scope.filterList, item),1);
		}
		/**=======================生成列表================================================**/
		//验证规则
		$scope.checkRule = function(item){
			commonService.show({template:'
<h8 class="modal-title">验证规则

'+
				'
'+
				'<input type="checkbox" id="{{regx}}"  ng-click="checked(regx)" ng-checked="isChecked(regx)" ></input>'+
				'{{REGULAR_EXPRESSION[regx].tip}}

',
				/**'
<button type="button" class="btn btn-default waves-effect" data-dismiss="modal"  ng-click="cancel()">取消</button>'+
				'<button type="button" class="btn btn-info waves-effect waves-light" ng-click="ok()">确定</button>
'**/
				controller:function($scope,$http,$uibModal,$log,$uibModalInstance,param) {
					$scope.REGULAR_EXPRESSION = REGULAR_EXPRESSION;
					$scope.keys = _.keys(REGULAR_EXPRESSION);
					var rule = eval(item.rule);
					$scope.isChecked = function(regx){
						return rule.indexOf(regx) > -1;
					}
					$scope.checked = function(regx){
						updateCheckBox(rule,regx);
						item.rule = JSON.stringify(rule);
					}
				},
				param:{item:item},
				callback:function(){

				}});

		}

		//生成列表
		$scope.createFieldData = function(){
			$.confirm({
				confirm: function(){
					commonService.ajax({url:"/search/sqlview/createField",data:JSON.stringify({sqlId:$scope.data.sqlId}),success:function(data){
						if(STATUS_CODE.SUCCESS == data.code){
							$scope.columnList = data.result;

							//消化
							$scope.$digest();
						}else{
							$.error(data.message);
						}
					}});
				}
			});
		}

		//移除选项
		$scope.removeField=function(item){
			$scope.columnList.splice(_.findIndex($scope.columnList, item),1);
		}

		//是否显示文本编辑器
		$scope.turnon = function(item){
			return item.componentType == 'VIEWSELECTOR';
		}

		//参数设置弹出框
		$scope.sqlSelectParamCtrl = function($scope,$http,$uibModal,$log,$uibModalInstance,param){

			//视图所有列
			$scope.columnList =  param.param;
			if(param.data.expand){
				$scope.expand = JSON.parse(param.data.expand);
			}else{
				$scope.expand={
					code:"",
					mappingList:[{
						valueKey:param.data.field, //数据键值
						textValue:"",//对应返回文本值
						icon:"md-add"
					}]
				};
			}

		$scope.adRecord = function(idx){

				//最后一条增加数据
				if($scope.expand.mappingList.length - 1 == idx){

					//往第一行插入数据
					$scope.expand.mappingList.splice(0,0,{
							valueKey:"",
							textValue:"",
							icon:"md-remove"});
				}else{

					//减去
					$scope.expand.mappingList.splice(idx,1);
				}
			}

			//选择器ok按钮
			$scope.ok = function() {
				param.data.expand = JSON.stringify($scope.expand);
				$uibModalInstance.close($scope.expand);
			};

			//取消
			$scope.cancel = function() {
				$uibModalInstance.dismiss('cancel');
			};
		}

		/**=======================按钮设置=======================================================================================================================**/
		$scope.btype = OPTION_BUTTON;
		$scope.winSize=WIN_SIZE;

		//添加按钮
		$scope.createButton = function(){
			var item = {};
			item.id = $.uuid();
			item.title="";
			item.icon="md-add";
			item.type= 0;
			item.lazyurl = "";
			item.url = "";
			item.ctrl = "";
			item.showWin= 0;
			item.winSize= 40;
			item.idx = $scope.buttonList.length + 1;
			$scope.buttonList.push(item);
		}
		$scope.removeButton = function(item){
			$scope.buttonList.splice(item,1);
		}

		//增、删、改、查
		$scope.insert = {id:CRUD_CODE.INSERT,title:"增加",icon:"md-add",type:1,url:"",showWin:1,winSize:50};
		$scope.update = {id:CRUD_CODE.UPDATE,title:"修改",icon:"ion-edit",type:0,url:"",showWin:1,winSize:50};
		$scope.remove = {id:CRUD_CODE.DELETE,title:"删除",icon:"ion-trash-a",type:0,url:"",showWin:0,winSize:""};
		$scope.view   = {id:CRUD_CODE.VIEW,title:"查看",icon:"ion-eye",type:0,url:"",showWin:1,winSize:50};

		$scope.crudCheck = function(item){
			var idx = getArrayIdxById($scope.buttonList,item);
			if(idx > -1){
				$scope.buttonList.splice(idx,1);
			}else{
				$scope.buttonList.push(item);
			}
		}
		$scope.isChecked = function(item){
			return getArrayIdxById($scope.buttonList,item) > -1;
		}

		//图标选择控制器
		var checkedIcon = function($scope,$http,$uibModal,$log,$uibModalInstance,param){
			$scope.ok=function($event){
				param.icon = $($event.target).attr("class");

				//关闭图标
				$uibModalInstance.close();
			}
		}

		//选择图标
		$scope.checkedIcon = function(item){
			var options = {templateUrl:"/templates/search/sqlview/iconSelector.html",controller:checkedIcon,
					param:item,
					size:80};
			commonService.show(options);
		}
		/**=======================树设置====================================**/
		$scope.nodeOpts = TREE_OPTIONS;
		//功能树形
		$scope.treeData = {
			url:"", //默认树形统一接口
			idKey:"id",
			name:"name",
			pIdKey: "parentId",
			rootPId: 0,
			isShow:0,
			nodeOpts:'ALL',
			width:2,
			sqlId:'',
			relationField:''
		};
		if($scope.data.treeData){
			$scope.treeData =  JSON.parse($scope.data.treeData);
		}

		//初始化
		$scope.verifyRule = $scope.treeData.isShow == 1 ? "[10001]" : "[]";

		//校验必填
		$scope.treeRequired = function(){
			$scope.verifyRule = $scope.treeData.isShow == 0 ? "[10001]" : "[]";
		}

	}

	//列表记录弹窗
	function baseIndexEdit(commonService,dataUrl,templateUrl,ctrl,param,callback,size) {

		if(param.id){
			/*根据选中ID获取最新数据*/
			commonService.post(dataUrl,param,function(data){

				if(data.code == STATUS_CODE.SUCCESS){
					param.row = data.result;
					commonService.show({templateUrl:templateUrl,controller:ctrl,param:param,callback:callback,size:(size||40)});
				}else{
					$.error(data.message);
				}
			});

		}else{
			commonService.show({templateUrl:templateUrl,controller:ctrl,param:param,callback:callback,size:(size||40)});
		}
	};

	return {
		restrict:'E',
		templateUrl:"templates/basic/directive/index.html",
		replace : false,
		transclude : false,
		scope:{
			url:"@",
			param:"=",
			returndata:"="
		},
		compile: function compile(tElement, tAttrs, transclude) {
			return {
				pre: function preLink(scope, iElement, iAttrs, controller) {

					//设置树参数
					function initTree(treeConfig){

						if(_.isUndefined(treeConfig)){
							return;
						}

						return {
							setting:{
								async:{
									url:treeConfig.url,
									type:"post",
									contentType: "application/json",
									enable:true
								},
								data:{
									simpleData:{
										enable: true, //不需要用户再把数据库中取出的 List 强行转换为复杂的 JSON 嵌套格式
										idKey: treeConfig.idKey,
										pIdKey: treeConfig.pIdKey,
										rootPId: treeConfig.rootPId
									}
								},
								callback: {
									onClick: function(event,treeId,node,idx){
										scope.parameter.treeNode=node;
										scope.$broadcast(scope.grid.id);
									}
								}
							}
						};
					}

					//工具栏
					scope.toolbar = {id:$.uuid()};

					//请求参数
					scope.parameter = $.extend({id:$.uuid()},scope.param);
					function success(data){
						data = eval('(' + data + ')');
						scope.sqlView = data;
						scope.grid = {
								id:$.uuid(),
								dataList:{},
								search:function(){
									scope.$broadcast(scope.grid.id);
								},
								url:data.controller + '/list',
								fieldData:data.fieldData
						};
						scope.treeconfig = initTree(data.treeData);

						//查询参数
						scope.parameter = {
								condition:scope.sqlView.filterData
						};
					}
					commonService.ajax({url:scope.url,success:success,type:"get",dataType:"text",async:false});

					//选中列表
					var checkedData= [];
					scope.rowClick = function(item,option){
						scope.returndata.option = option;

						//单选
						if(_.isEqual(GRID_OPTIONS.SINGLE, option)){
							checkedData[0] = item;
						}else{
							updateCheckBox(checkedData,item);
						}
						scope.returndata.data = checkedData;
					}

					scope.crud = function crud(item,func){
						switch(func.id){
						case CRUD_CODE.INSERT:
						case CRUD_CODE.UPDATE:
							item = item || {id:""};
							baseIndexEdit(commonService,scope.sqlView.controller + '/findById',func.url,modalDialog,{id:item.id,sqlView:scope.sqlView},scope.grid.search,80);
							break;
						case CRUD_CODE.DELETE://删除
							remove(commonService,scope.sqlView.controller + '/delete',{id:item.id},scope.search);
							break;

						case CRUD_CODE.VIEW: //查
							break;
						}
					}
					//判断是否显示树
					scope.showtree = function(){
						return !_.isUndefined(scope.treeconfig);
					}
				}
			}
		}
	};
});<|endoftext|>


<commit_before>$(document).ready(function() {

	// Winning values
	var winningValues = [';763952?'];

	// Select input field
	$('#value').focus();

	// Behave when enter key is pressed/card is swiped
	$('#value').bind("enterKey",function(){

		// Clear win/loss
		$('#win, #loss').css('color', '#ffffff');

		var scannedValue = $(this).val();

		// Check for a valid scan
		if (/^(;\d+\?$)/.test(scannedValue)){
			$('#status').text('Card Scanned!  Loading...');

			// Add suspense
			setTimeout(function(){
				$('#status').text('Waiting for Scan...');
				// Check to see if entered value is a winner
				if ($.inArray(scannedValue, winningValues) != -1){
					$('#win').css('color', 'red');
					console.log('winner: ' + scannedValue);
				} else {
					$('#loss').css('color', 'red');
					console.log('loser: ' + scannedValue);
				}
			},2000);

		} else {
			$('#status').text('Invalid Scan!  Please try again');
		}

	   // Empty the input field
	   $(this).val('')
	});

	// Enter key function
	$('#value').keyup(function(e){
	    if(e.keyCode == 13)
	    {
	        $(this).trigger("enterKey");
	    }
	});

	// Prevent the user from unfocusing the input field
	$('#value').focusout(function() {
		$('#value').focus();
	});

});
<commit_msg>JS formatting<commit_after>$(document).ready(function() {

	// Select the off-screen input field on page load
	$('#value').focus();

	// Predetermined winning values
	var winningValues = [';763952?'];

	// Enter key press to be binded
	$('#value').keyup(function(e){
	    if(e.keyCode == 13)
	    {
	        $(this).trigger("enterKey");
	    }
	});

	// Prevent the user from defocussing the input field
	$('#value').focusout(function() {
		$('#value').focus();
	});

	// Enter key is pressed/card is swiped
	$('#value').bind("enterKey",function(){

		// Clear win/loss
		$('#win, #loss').css('color', '#ffffff');

		var scannedValue = $(this).val();

		// Check for a valid scan using the regular expression: start of string, beginning capturing group, semi-colon, any number of digits, question mark, ending capturing group, end of string
		// Ex. ;763952?
		if (/^(;\d+\?)$/.test(scannedValue)){
			$('#status').text('Card Scanned!  Loading...');

			// Add suspense
			setTimeout(function(){
				$('#status').text('Waiting for Scan...');
				// Check to see if entered value is a winner
				if ($.inArray(scannedValue, winningValues) != -1){
					$('#win').css('color', 'red');
					console.log('winner: ' + scannedValue);
				} else {
					$('#loss').css('color', 'red');
					console.log('loser: ' + scannedValue);
				}
			},2000);

		} else {
			$('#status').text('Invalid Scan!  Please try again');
		}

	   // Empty the input field for the next scan
	   $(this).val('')
	});

});
<|endoftext|>


<commit_before>const router = module.exports = express.Router(); // eslint-disable-line new-cap
const keys = ["mode", "tabType", "tabSize", "theme", "keyMap"];

router.post("/", async (req, res) => {
	if(!req.user) {
		res.status(401).send({ message: "Not logged in" }).end();
	} else if(Object.keys(req.body).length !== keys.length) {
		res.status(400).send({ message: "Amount of keys is not 5" }).end();
	} else if(Object.keys(req.body).some(key => !~keys.indexOf(key))) {
		res.status(400).send({ message: "Invalid keys" }).end();
	} else {
		await r.table("settings").insert({ id: req.user.id, settings: req.body }).run();
		res.status(204).end();
	}
});
<commit_msg>fix settings<commit_after>const router = module.exports = express.Router(); // eslint-disable-line new-cap
const keys = ["mode", "tabType", "tabSize", "theme", "keyMap"];

router.post("/", async (req, res) => {
	if(!req.user) {
		res.status(401).send({ message: "Not logged in" }).end();
	} else if(Object.keys(req.body).length !== keys.length) {
		res.status(400).send({ message: "Amount of keys is not 5" }).end();
	} else if(Object.keys(req.body).some(key => !~keys.indexOf(key))) {
		res.status(400).send({ message: "Invalid keys" }).end();
	} else {
		if(req.user.options) {
			await r.table("settings").insert({ id: req.user.id, settings: req.body }).run();
		} else {
			await r.table("settings").get(req.user.id).update({ settings: req.body }).run();
		}

		res.status(204).end();
	}
});
<|endoftext|>


<commit_before>const routeHelper = require('../../helpers/routes/route-helper')
const supertest = require('supertest')
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const TaskEnums = require('../../../app/constants/tasks-enum')
require('sinon-bluebird')

const ValidationError = require('../../../app/services/errors/validation-error')

describe('routes/technical-help', function () {
  const ROUTE = `/technical-help`
  const VALID_DATA = {
    name: 'Joe Bloggs',
    PhoneNumber: '02874628481',
    issue: 'Testing problems are occuring'
  }

  var app

  var technicalHelpStub
  var insertTaskStub

  beforeEach(function () {
    technicalHelpStub = sinon.stub()
    insertTaskStub = sinon.stub().resolves()

    var route = proxyquire('../../../app/routes/technical-help', {
      '../services/domain/technical-help': technicalHelpStub,
      '../services/data/insert-task': insertTaskStub
    })
    app = routeHelper.buildApp(route)
  })

  describe(`GET ${ROUTE}`, function () {
    it('should respond with a 200', function () {
      return supertest(app)
        .get(ROUTE)
        .expect(200)
    })
  })

  describe(`POST ${ROUTE}`, function () {
    it('should respond with a 302', function () {
      technicalHelpStub.returns(VALID_DATA)
      return supertest(app)
        .post(ROUTE)
        .send(VALID_DATA)
        .expect(302)
        .expect(function () {
          sinon.assert.calledWith(technicalHelpStub, VALID_DATA.name, VALID_DATA.PhoneNumber, VALID_DATA.issue)
          sinon.assert.calledWith(insertTaskStub, null, null, null, TaskEnums.TECHNICAL_HELP_SUBMITTED, `${VALID_DATA.name}~~${VALID_DATA.PhoneNumber}~~${VALID_DATA.issue}`)
        })
    })

    it('should respond with a 400 if validation fails', function () {
      technicalHelpStub.throws(new ValidationError({ 'name': {} }))
      return supertest(app)
        .post(ROUTE)
        .expect(400)
    })
  })
})
<commit_msg>Added unit test to cover untested branch. (#384)<commit_after>const routeHelper = require('../../helpers/routes/route-helper')
const supertest = require('supertest')
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const TaskEnums = require('../../../app/constants/tasks-enum')
require('sinon-bluebird')

const ValidationError = require('../../../app/services/errors/validation-error')

describe('routes/technical-help', function () {
  const ROUTE = `/technical-help`
  const VALID_DATA = {
    name: 'Joe Bloggs',
    PhoneNumber: '02874628481',
    issue: 'Testing problems are occuring'
  }

  var app

  var technicalHelpStub
  var insertTaskStub

  beforeEach(function () {
    technicalHelpStub = sinon.stub()
    insertTaskStub = sinon.stub().resolves()

    var route = proxyquire('../../../app/routes/technical-help', {
      '../services/domain/technical-help': technicalHelpStub,
      '../services/data/insert-task': insertTaskStub
    })
    app = routeHelper.buildApp(route)
  })

  describe(`GET ${ROUTE}`, function () {
    it('should respond with a 200', function () {
      return supertest(app)
        .get(ROUTE)
        .expect(200)
    })
  })

  describe(`POST ${ROUTE}`, function () {
    it('should respond with a 302', function () {
      technicalHelpStub.returns(VALID_DATA)
      return supertest(app)
        .post(ROUTE)
        .send(VALID_DATA)
        .expect(302)
        .expect(function () {
          sinon.assert.calledWith(technicalHelpStub, VALID_DATA.name, VALID_DATA.PhoneNumber, VALID_DATA.issue)
          sinon.assert.calledWith(insertTaskStub, null, null, null, TaskEnums.TECHNICAL_HELP_SUBMITTED, `${VALID_DATA.name}~~${VALID_DATA.PhoneNumber}~~${VALID_DATA.issue}`)
        })
    })

    it('should respond with a 400 if validation fails', function () {
      technicalHelpStub.throws(new ValidationError())
      return supertest(app)
        .post(ROUTE)
        .expect(400)
    })

    it('should respond with a 500 if a non-validation error occurs.', function () {
      technicalHelpStub.throws(new Error())
      return supertest(app)
        .post(ROUTE)
        .expect(500)
    })
  })
})
<|endoftext|>


<commit_before>import Ember from 'ember';
import DS from 'ember-data';

export default DS.Model.extend({
  machineIp: DS.attr('string'),
  timeStamp: DS.attr('string'),
  tableName: DS.attr('string'),
  tableRowId: DS.attr('string'),
  administrator: DS.belongsTo('user', {async: true}),
  sessions: DS.hasMany('session', {async: true}),
  programs: DS.hasMany('program', {async: true}),
  courses: DS.hasMany('course', {async: true}),
  programYears: DS.hasMany('program-year', {async: true}),
  relatedCounts: Ember.computed.collect(
    'sessions.length',
    'courses.length',
    'programs.length',
    'programYears.length'
  ),
  totalRelated: Ember.computed.sum('relatedCounts'),
});
<commit_msg>Remove totalRelated from publish-event model<commit_after>import Ember from 'ember';
import DS from 'ember-data';

export default DS.Model.extend({
  machineIp: DS.attr('string'),
  timeStamp: DS.attr('string'),
  tableName: DS.attr('string'),
  tableRowId: DS.attr('string'),
  administrator: DS.belongsTo('user', {async: true}),
  sessions: DS.hasMany('session', {async: true}),
  programs: DS.hasMany('program', {async: true}),
  courses: DS.hasMany('course', {async: true}),
  programYears: DS.hasMany('program-year', {async: true}),
});
<|endoftext|>


<commit_before>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import ContainerPlugin from 'base/container_plugin'
import Events from 'base/events'
import $ from "clappr-zepto"

export default class StatsPlugin extends ContainerPlugin {
  get name() { return 'stats' }

  constructor(container) {
    super(container)
    this.setInitialAttrs()
    this.reportInterval = this.options.reportInterval || 5000
    this.state = "IDLE"
  }

  bindEvents() {
    this.listenTo(this.container.playback, Events.PLAYBACK_PLAY, this.onPlay)
    this.listenTo(this.container, Events.CONTAINER_STOP, this.onStop)
    this.listenTo(this.container, Events.CONTAINER_DESTROYED, this.onStop)
    this.listenTo(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering)
    this.listenTo(this.container, Events.CONTAINER_STATE_BUFFERFULL, this.onBufferFull)
    this.listenTo(this.container, Events.CONTAINER_STATS_ADD, this.onStatsAdd)
    this.listenTo(this.container, Events.CONTAINER_BITRATE, this.onStatsAdd)
    this.listenTo(this.container.playback, Events.PLAYBACK_STATS_ADD, this.onStatsAdd)
  }

  setInitialAttrs() {
    this.firstPlay = true
    this.startupTime = 0
    this.rebufferingTime = 0
    this.watchingTime = 0
    this.rebuffers = 0
    this.externalMetrics = {}
  }

  onPlay() {
    this.state = "PLAYING"
    this.watchingTimeInit = Date.now()
    if (!this.intervalId) {
      this.intervalId = setInterval(this.report.bind(this), this.reportInterval)
    }
  }

  onStop() {
    clearInterval(this.intervalId)
    this.intervalId = undefined
    this.state = "STOPPED"
  }

  onBuffering() {
    if (this.firstPlay) {
      this.startupTimeInit = Date.now()
    } else {
      this.rebufferingTimeInit = Date.now()
    }
    this.state = "BUFFERING"
    this.rebuffers++
  }

  onBufferFull() {
    if (this.firstPlay && !!this.startupTimeInit) {
      this.firstPlay = false
      this.startupTime = Date.now() - this.startupTimeInit
      this.watchingTimeInit = Date.now()
    } else if (!!this.rebufferingTimeInit) {
      this.rebufferingTime += this.getRebufferingTime()
    }
    this.rebufferingTimeInit = undefined
    this.state = "PLAYING"
  }

  getRebufferingTime() {
    return Date.now() - this.rebufferingTimeInit
  }

  getWatchingTime() {
    var totalTime = (Date.now() - this.watchingTimeInit)
    return totalTime - this.rebufferingTime
  }

  isRebuffering() {
    return !!this.rebufferingTimeInit
  }

  onStatsAdd(metric) {
    $.extend(this.externalMetrics, metric)
  }

  getStats() {
    var metrics = {
      startupTime:     this.startupTime,
      rebuffers:       this.rebuffers,
      rebufferingTime: this.isRebuffering()? this.rebufferingTime + this.getRebufferingTime(): this.rebufferingTime,
      watchingTime:    this.isRebuffering()? this.getWatchingTime() - this.getRebufferingTime(): this.getWatchingTime()
    }
    $.extend(metrics, this.externalMetrics)
    return metrics
  }

  report() {
    this.container.statsReport(this.getStats())
  }
}
<commit_msg>stats: fix plugin continuously reporting after video end<commit_after>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import ContainerPlugin from 'base/container_plugin'
import Events from 'base/events'
import $ from "clappr-zepto"

export default class StatsPlugin extends ContainerPlugin {
  get name() { return 'stats' }

  constructor(container) {
    super(container)
    this.setInitialAttrs()
    this.reportInterval = this.options.reportInterval || 5000
    this.state = "IDLE"
  }

  bindEvents() {
    this.listenTo(this.container.playback, Events.PLAYBACK_PLAY, this.onPlay)
    this.listenTo(this.container, Events.CONTAINER_STOP, this.onStop)
    this.listenTo(this.container, Events.CONTAINER_ENDED, this.onStop)
    this.listenTo(this.container, Events.CONTAINER_DESTROYED, this.onStop)
    this.listenTo(this.container, Events.CONTAINER_STATE_BUFFERING, this.onBuffering)
    this.listenTo(this.container, Events.CONTAINER_STATE_BUFFERFULL, this.onBufferFull)
    this.listenTo(this.container, Events.CONTAINER_STATS_ADD, this.onStatsAdd)
    this.listenTo(this.container, Events.CONTAINER_BITRATE, this.onStatsAdd)
    this.listenTo(this.container.playback, Events.PLAYBACK_STATS_ADD, this.onStatsAdd)
  }

  setInitialAttrs() {
    this.firstPlay = true
    this.startupTime = 0
    this.rebufferingTime = 0
    this.watchingTime = 0
    this.rebuffers = 0
    this.externalMetrics = {}
  }

  onPlay() {
    this.state = "PLAYING"
    this.watchingTimeInit = Date.now()
    if (!this.intervalId) {
      this.intervalId = setInterval(this.report.bind(this), this.reportInterval)
    }
  }

  onStop() {
    clearInterval(this.intervalId)
    this.report()
    this.intervalId = undefined
    this.state = "STOPPED"
  }

  onBuffering() {
    if (this.firstPlay) {
      this.startupTimeInit = Date.now()
    } else {
      this.rebufferingTimeInit = Date.now()
    }
    this.state = "BUFFERING"
    this.rebuffers++
  }

  onBufferFull() {
    if (this.firstPlay && !!this.startupTimeInit) {
      this.firstPlay = false
      this.startupTime = Date.now() - this.startupTimeInit
      this.watchingTimeInit = Date.now()
    } else if (!!this.rebufferingTimeInit) {
      this.rebufferingTime += this.getRebufferingTime()
    }
    this.rebufferingTimeInit = undefined
    this.state = "PLAYING"
  }

  getRebufferingTime() {
    return Date.now() - this.rebufferingTimeInit
  }

  getWatchingTime() {
    var totalTime = (Date.now() - this.watchingTimeInit)
    return totalTime - this.rebufferingTime
  }

  isRebuffering() {
    return !!this.rebufferingTimeInit
  }

  onStatsAdd(metric) {
    $.extend(this.externalMetrics, metric)
  }

  getStats() {
    var metrics = {
      startupTime:     this.startupTime,
      rebuffers:       this.rebuffers,
      rebufferingTime: this.isRebuffering()? this.rebufferingTime + this.getRebufferingTime(): this.rebufferingTime,
      watchingTime:    this.isRebuffering()? this.getWatchingTime() - this.getRebufferingTime(): this.getWatchingTime()
    }
    $.extend(metrics, this.externalMetrics)
    return metrics
  }

  report() {
    this.container.statsReport(this.getStats())
  }
}
<|endoftext|>


<commit_before>$(document).ready(function() {

	// Select the off-screen input field on page load
	$('#value').focus();

	// Prevent the user from defocussing the input field
	$('#value').focusout(function() {
		$('#value').focus();
	});

	// Enter key press to be binded
	$('#value').keyup(function(e){
	    if(e.keyCode == 13) {
	        $(this).trigger("enterKey");
	    }
	});

	// Enter key is pressed/card is swiped
	$('#value').bind("enterKey",function(){

		// Clear win and loss
		$('.category').css('color', '#ffffff');

		var scannedValue = $(this).val();

		// Check for a valid scan using the regular expression: start of string, beginning capturing group, semi-colon, any number of digits, question mark, ending capturing group, end of string
		// Ex. ;763952?
		if (/^(;\d+\?)$/.test(scannedValue)){
			$('#status').text('Card Scanned!  Loading...');

			// Add suspense
			setTimeout(function(){
				$('#status').text('Waiting for Scan...');
				// Check to see if entered value is a winner
				switch (scannedValue){
					case ';612763?':
						$('#promotional').css('color', 'red');
						break;
					case ';952507?':
						$('#prize').css('color', 'red');
						break;
					default:
						$('#loss').css('color', 'red');
						break;
				}
			},2000);

		} else {
			$('#status').text('Invalid Scan!  Please try again');
		}

	   // Empty the input field for the next scan
	   $(this).val('');
	});

});
<commit_msg>Pointless tab formatting<commit_after>$(document).ready(function() {

	// Select the off-screen input field on page load
	$('#value').focus();

	// Prevent the user from defocussing the input field
	$('#value').focusout(function() {
		$('#value').focus();
	});

	// Enter key press to be binded
	$('#value').keyup(function(e){
		if(e.keyCode == 13) {
			$(this).trigger("enterKey");
		}
	});

	// Enter key is pressed/card is swiped
	$('#value').bind("enterKey",function(){

		// Clear win and loss
		$('.category').css('color', '#ffffff');

		var scannedValue = $(this).val();

		// Check for a valid scan using the regular expression: start of string, beginning capturing group, semi-colon, any number of digits, question mark, ending capturing group, end of string
		// Ex. ;763952?
		if (/^(;\d+\?)$/.test(scannedValue)){
			$('#status').text('Card Scanned!  Loading...');

			// Add suspense
			setTimeout(function(){
				$('#status').text('Waiting for Scan...');
				// Check to see if entered value is a winner
				switch (scannedValue){
					case ';612763?':
						$('#promotional').css('color', 'red');
						break;
					case ';952507?':
						$('#prize').css('color', 'red');
						break;
					default:
						$('#loss').css('color', 'red');
						break;
				}
			},2000);

		} else {
			$('#status').text('Invalid Scan!  Please try again');
		}

		// Empty the input field for the next scan
		$(this).val('');
	});

});
<|endoftext|>


<commit_before>/* autogenerated by the update_version script */
exports.version=1519875501;
<commit_msg>update version<commit_after>/* autogenerated by the update_version script */
exports.version=1520007876;
<|endoftext|>