<commit_before>import Button from 'react-toolbox/lib/button';
import Dropdown from 'react-toolbox/lib/dropdown';
import ProgressBar from 'react-toolbox/lib/progress_bar';
import React, { Component, PropTypes } from 'react';
import { Card, CardText } from 'react-toolbox/lib/card';
import { ipcRenderer as ipc } from 'electron';
import { bindStateForComponent } from '../utils/parameters';
import Label from '../components/Label';
import Column from '../components/Column';
import ImmutablePropTypes from 'react-immutable-proptypes';
import eeprom from '../utils/eeprom';

class Sidebar extends Component {
  static propTypes = {
    setParamsFromEEPROM: PropTypes.func.isRequired,
    showError: PropTypes.func.isRequired,
    showInfo: PropTypes.func.isRequired,
    state: ImmutablePropTypes.map,
  }

  constructor(props) {
    super(props);
    ipc.on('serial-ports', this._onSerialPortsReceived);
    ipc.on('osd-config', this._onOSDConfigReceived);
    ipc.on('osd-config-written', this._onOSDConfigWritten);
    ipc.on('osd-file-written', this._onConfigFileWritten);
    ipc.on('osd-file-read', this._onConfigFileRead);
    ipc.on('firmware-uploaded', this._onFirmwareUploaded);
    ipc.on('error', this._onError);
    ipc.on('progress', this._onProgress);
    ipc.send('get-serial-ports');
  }

  state = {
    serialPorts: [],
    serialPort: null,
    connected: false,
    connecting: false,
    readingOSD: false,
    writingOSD: false,
    uploading: false,
    progress: 0,
  }

  _onError = (e, error) => {
    this.props.showError(error);
    this.setState({ ...this.state,
      readingOSD: false, writingOSD: false, uploading: false, progress: 0
    });
  }

  _onProgress = (_, progress) => {
    this.setState({ ...this.state, progress });
  }

  _onSerialPortsReceived = (_, serialPorts) => {
    let serialPort = this.state.serialPort;
    if (serialPorts.length === 0) {
      serialPort = null;
    } else if (serialPorts.indexOf(serialPort) < 0) {
      serialPort = serialPorts[0].comName;
    }
    this.setState({ ...this.state, serialPorts, serialPort });
  }

  _onOSDConfigReceived = (_, eepromData) => {
    this.setState({ ...this.state, readingOSD: false, progress: 0 });
    this.props.setParamsFromEEPROM(eepromData);
    this.props.showInfo('finished reading osd configuration');
  }

  _onOSDConfigWritten = () => {
    this.setState({ ...this.state, writingOSD: false, progress: 0 });
    this.props.showInfo('finished writing osd configuration');
  }

  _onConfigFileWritten = () => {
    this.props.showInfo('wrote configuration to file');
  }

  _onConfigFileRead = (_, eepromData) => {
    this.props.setParamsFromEEPROM(eepromData);
    this.props.showInfo('read configuration from file');
  }

  _onFirmwareUploaded = () => {
    this.setState({ ...this.state, uploading: false, progress: 0 });
    this.props.showInfo('finished uploading firmware');
  }

  _onSerialPortChange = (serialPort) => {
    this.setState({ ...this.state, serialPort });
  }

  _readFromOSD = () => {
    this.props.showInfo('reading osd configuration');
    this.setState({ ...this.state, readingOSD: true });
    ipc.send('read-osd', this.state.serialPort);
  }

  _writeToOSD = () => {
    this.props.showInfo('writing osd configuration');
    this.setState({ ...this.state, writingOSD: true });
    ipc.send('write-osd', this.state.serialPort, eeprom.fromParameters(this.props.state));
  }

  _writeToFile = () => {
    ipc.send('write-file', eeprom.fromParameters(this.props.state));
  }

  _readFile = () => {
    ipc.send('read-file');
  }

  _uploadFirmware = () => {
    this.setState({ ...this.state, uploading: true });
    ipc.send('upload-firmware', this.state.serialPort);
  }

  _loadDefaults = () => {
    this.props.setParamsFromEEPROM(eeprom.defaultEEPROM);
    this.props.showInfo('loaded default osd configuration');
  }

  _refreshSerialPorts() {
    ipc.send('get-serial-ports');
  }

  _renderReadOSDButton() {
    const { readingOSD, writingOSD, uploading } = this.state;
    const disabled = readingOSD || writingOSD || uploading;
    const label = readingOSD ? 'reading from osd' : 'read from osd';
    return (
      <Button onClick={this._readFromOSD} label={label}
        icon="file_download" disabled={disabled} raised
      />
    );
  }

  _renderWriteOSDButton() {
    const { readingOSD, writingOSD, uploading } = this.state;
    const disabled = readingOSD || writingOSD || uploading;
    const label = writingOSD ? 'writing to osd' : 'write to osd';
    return (
      <Button onClick={this._writeToOSD} label={label}
        icon="file_upload" disabled={disabled} raised
      />
    );
  }

  _renderLoadDefaultButton() {
    return (
      <Button label="load defaults" icon="settings_backup_restore"
        onClick={this._loadDefaults} raised
      />
    );
  }

  _renderUploadFirmwareButton() {
    const { readingOSD, writingOSD, uploading } = this.state;
    const disabled = readingOSD || writingOSD || uploading;
    const label = uploading ? 'uploading firmware' : 'upload firmware';
    return (
      <Button onClick={this._uploadFirmware} label={label}
        icon="present_to_all" raised disabled={disabled}
      />
    );
  }

  _renderRefreshButton() {
    if (process.platform === 'linux') {
      return;
    }

    return (
      <Button style={{ margin: '2rem 0.5rem' }} label="refresh"
        onClick={this._refreshSerialPorts} raised
      />
    );
  }

  _renderSerialPortDropdown() {
    const serialPorts = this.state.serialPorts.map((port) => {
      return { value: port.comName, label: port.comName };
    });

    return (
      <Dropdown
        auto
        value={this.state.serialPort}
        source={serialPorts}
        onChange={this._onSerialPortChange}
        label="serial port"
      />
    );
  }

  _renderNoSerialPorts() {
    return (

No serial ports found

    );
  }

  _renderProgressBar() {
    if (!this.state.uploading && !this.state.writingOSD && !this.state.readingOSD) {
      return;
    }

    return (<ProgressBar value={this.state.progress} mode="determinate" />);
  }

  render() {
    let serialPortSelector;

    if (this.state.serialPorts.length) {
      serialPortSelector = this._renderSerialPortDropdown();
    } else {
      serialPortSelector = this._renderNoSerialPorts();
    }

    return (
      <Card className="connection">
        <CardText>
          <Column width={75}>
            {serialPortSelector}
          </Column>
          <Column width={25}>
            {this._renderRefreshButton()}
          </Column>

          {this._renderProgressBar()}

          {this._renderWriteOSDButton()}
          {this._renderReadOSDButton()}

          {this._renderUploadFirmwareButton()}
          {this._renderLoadDefaultButton()}

          <Button onClick={this._writeToFile} label="save to file" icon="save" raised />
          <Button onClick={this._readFile} label="read from file" icon="folder_open" raised />
        </CardText>
      </Card>
    );
  }
}

export default(bindStateForComponent('sidebar', Sidebar));
<commit_msg>Rename upload to flash to save space<commit_after>import Button from 'react-toolbox/lib/button';
import Dropdown from 'react-toolbox/lib/dropdown';
import ProgressBar from 'react-toolbox/lib/progress_bar';
import React, { Component, PropTypes } from 'react';
import { Card, CardText } from 'react-toolbox/lib/card';
import { ipcRenderer as ipc } from 'electron';
import { bindStateForComponent } from '../utils/parameters';
import Label from '../components/Label';
import Column from '../components/Column';
import ImmutablePropTypes from 'react-immutable-proptypes';
import eeprom from '../utils/eeprom';

class Sidebar extends Component {
  static propTypes = {
    setParamsFromEEPROM: PropTypes.func.isRequired,
    showError: PropTypes.func.isRequired,
    showInfo: PropTypes.func.isRequired,
    state: ImmutablePropTypes.map,
  }

  constructor(props) {
    super(props);
    ipc.on('serial-ports', this._onSerialPortsReceived);
    ipc.on('osd-config', this._onOSDConfigReceived);
    ipc.on('osd-config-written', this._onOSDConfigWritten);
    ipc.on('osd-file-written', this._onConfigFileWritten);
    ipc.on('osd-file-read', this._onConfigFileRead);
    ipc.on('firmware-uploaded', this._onFirmwareUploaded);
    ipc.on('error', this._onError);
    ipc.on('progress', this._onProgress);
    ipc.send('get-serial-ports');
  }

  state = {
    serialPorts: [],
    serialPort: null,
    connected: false,
    connecting: false,
    readingOSD: false,
    writingOSD: false,
    uploading: false,
    progress: 0,
  }

  _onError = (e, error) => {
    this.props.showError(error);
    this.setState({ ...this.state,
      readingOSD: false, writingOSD: false, uploading: false, progress: 0
    });
  }

  _onProgress = (_, progress) => {
    this.setState({ ...this.state, progress });
  }

  _onSerialPortsReceived = (_, serialPorts) => {
    let serialPort = this.state.serialPort;
    if (serialPorts.length === 0) {
      serialPort = null;
    } else if (serialPorts.indexOf(serialPort) < 0) {
      serialPort = serialPorts[0].comName;
    }
    this.setState({ ...this.state, serialPorts, serialPort });
  }

  _onOSDConfigReceived = (_, eepromData) => {
    this.setState({ ...this.state, readingOSD: false, progress: 0 });
    this.props.setParamsFromEEPROM(eepromData);
    this.props.showInfo('finished reading osd configuration');
  }

  _onOSDConfigWritten = () => {
    this.setState({ ...this.state, writingOSD: false, progress: 0 });
    this.props.showInfo('finished writing osd configuration');
  }

  _onConfigFileWritten = () => {
    this.props.showInfo('wrote configuration to file');
  }

  _onConfigFileRead = (_, eepromData) => {
    this.props.setParamsFromEEPROM(eepromData);
    this.props.showInfo('read configuration from file');
  }

  _onFirmwareUploaded = () => {
    this.setState({ ...this.state, uploading: false, progress: 0 });
    this.props.showInfo('finished uploading firmware');
  }

  _onSerialPortChange = (serialPort) => {
    this.setState({ ...this.state, serialPort });
  }

  _readFromOSD = () => {
    this.props.showInfo('reading osd configuration');
    this.setState({ ...this.state, readingOSD: true });
    ipc.send('read-osd', this.state.serialPort);
  }

  _writeToOSD = () => {
    this.props.showInfo('writing osd configuration');
    this.setState({ ...this.state, writingOSD: true });
    ipc.send('write-osd', this.state.serialPort, eeprom.fromParameters(this.props.state));
  }

  _writeToFile = () => {
    ipc.send('write-file', eeprom.fromParameters(this.props.state));
  }

  _readFile = () => {
    ipc.send('read-file');
  }

  _uploadFirmware = () => {
    this.setState({ ...this.state, uploading: true });
    ipc.send('upload-firmware', this.state.serialPort);
  }

  _loadDefaults = () => {
    this.props.setParamsFromEEPROM(eeprom.defaultEEPROM);
    this.props.showInfo('loaded default osd configuration');
  }

  _refreshSerialPorts() {
    ipc.send('get-serial-ports');
  }

  _renderReadOSDButton() {
    const { readingOSD, writingOSD, uploading } = this.state;
    const disabled = readingOSD || writingOSD || uploading;
    const label = readingOSD ? 'reading from osd' : 'read from osd';
    return (
      <Button onClick={this._readFromOSD} label={label}
        icon="file_download" disabled={disabled} raised
      />
    );
  }

  _renderWriteOSDButton() {
    const { readingOSD, writingOSD, uploading } = this.state;
    const disabled = readingOSD || writingOSD || uploading;
    const label = writingOSD ? 'writing to osd' : 'write to osd';
    return (
      <Button onClick={this._writeToOSD} label={label}
        icon="file_upload" disabled={disabled} raised
      />
    );
  }

  _renderLoadDefaultButton() {
    return (
      <Button label="load defaults" icon="settings_backup_restore"
        onClick={this._loadDefaults} raised
      />
    );
  }

  _renderUploadFirmwareButton() {
    const { readingOSD, writingOSD, uploading } = this.state;
    const disabled = readingOSD || writingOSD || uploading;
    const label = uploading ? 'flashing firmware' : 'flash firmware';
    return (
      <Button onClick={this._uploadFirmware} label={label}
        icon="present_to_all" raised disabled={disabled}
      />
    );
  }

  _renderRefreshButton() {
    if (process.platform === 'linux') {
      return;
    }

    return (
      <Button style={{ margin: '2rem 0.5rem' }} label="refresh"
        onClick={this._refreshSerialPorts} raised
      />
    );
  }

  _renderSerialPortDropdown() {
    const serialPorts = this.state.serialPorts.map((port) => {
      return { value: port.comName, label: port.comName };
    });

    return (
      <Dropdown
        auto
        value={this.state.serialPort}
        source={serialPorts}
        onChange={this._onSerialPortChange}
        label="serial port"
      />
    );
  }

  _renderNoSerialPorts() {
    return (

No serial ports found

    );
  }

  _renderProgressBar() {
    if (!this.state.uploading && !this.state.writingOSD && !this.state.readingOSD) {
      return;
    }

    return (<ProgressBar value={this.state.progress} mode="determinate" />);
  }

  render() {
    let serialPortSelector;

    if (this.state.serialPorts.length) {
      serialPortSelector = this._renderSerialPortDropdown();
    } else {
      serialPortSelector = this._renderNoSerialPorts();
    }

    return (
      <Card className="connection">
        <CardText>
          <Column width={75}>
            {serialPortSelector}
          </Column>
          <Column width={25}>
            {this._renderRefreshButton()}
          </Column>

          {this._renderProgressBar()}

          {this._renderWriteOSDButton()}
          {this._renderReadOSDButton()}

          {this._renderUploadFirmwareButton()}
          {this._renderLoadDefaultButton()}

          <Button onClick={this._writeToFile} label="save to file" icon="save" raised />
          <Button onClick={this._readFile} label="read from file" icon="folder_open" raised />
        </CardText>
      </Card>
    );
  }
}

export default(bindStateForComponent('sidebar', Sidebar));
<|endoftext|>


<commit_before>'use strict';

// Make sure to include the `ui.router` module as a dependency
var app = angular.module('algorea', ['ui.router', 'ui.bootstrap', 'franceIOILogin', 'ngSanitize','small-ui-confirm']);

app.run(function($window) {
	$window.ga('create', 'UA-9457907-5', 'auto');
})
<commit_msg>minification-proof<commit_after>'use strict';

// Make sure to include the `ui.router` module as a dependency
var app = angular.module('algorea', ['ui.router', 'ui.bootstrap', 'franceIOILogin', 'ngSanitize','small-ui-confirm']);

app.run(['$window', function($window) {
	$window.ga('create', 'UA-9457907-5', 'auto');
}]);
<|endoftext|>


<commit_before>import createLWTheme from './createThemeDefaults.js';
import grey from '@material-ui/core/colors/grey';
import deepOrange from '@material-ui/core/colors/deepOrange';

const sansSerifStack = [
  'Calibri',
  '"Gill Sans"',
  '"Gill Sans MT"',
  "Myriad Pro",
  'Myriad',
  '"DejaVu Sans Condensed"',
  '"Liberation Sans"',
  '"Nimbus Sans L"',
  'Tahoma',
  'Geneva',
  '"Helvetica Neue"',
  'Helvetica',
  'Arial',
  'sans-serif'
].join(',')

const serifStack = [
  'warnock-pro',
  'Palatino',
  '"Palatino Linotype"',
  '"Palatino LT STD"',
  '"Book Antiqua"',
  'Georgia',
  'serif'
].join(',')

const palette = {
  primary: {
    main: '#5f9b65',
  },
  secondary: {
    main: '#5f9b65',
  },
  lwTertiary: {
    main: "#799b47"
  },
  error: {
    main: deepOrange[900]
  },
  background: {
    default: '#fff'
  }
}

const theme = createLWTheme({
  palette: palette,
  typography: {
    fontFamily: sansSerifStack,
    postStyle: {
      fontFamily: serifStack,
      linkUnderlinePosition: "72%",
    },
    headerStyle: {
      fontFamily: serifStack,
      linkUnderlinePosition: "72%",
    },
    body2: {
      fontSize: "1.16rem"
    },
    commentStyle: {
      fontFamily: sansSerifStack
    },
    headline: {
      fontFamily: serifStack,
    },
    subheading: {
      fontFamily: serifStack,
    },
    title: {
      fontFamily: serifStack,
      fontWeight: 500,
    }
  },
  overrides: {
    MuiAppBar: {
      colorDefault: {
        backgroundColor: grey[50],
      }
    },
    PostsVote: {
      voteScores: {
        margin: "25% 15% 15% 15%"
      }
    },
    MuiTooltip: {
      tooltip: {
        fontSize: "1rem",
        padding: ".7rem",
      }
    },
    MuiDialogContent: {
      root: {
        fontFamily: sansSerifStack,
        fontSize: "1.16rem",
        lineHeight: "1.5em"
      }
    },
    MuiMenuItem: {
      root: {
        fontFamily: sansSerifStack,
        color: grey[800],
        fontSize: "1.1rem",
        lineHeight: "1em"
      }
    },
    MuiListItem: {
      root: {
        paddingTop: 8,
        paddingBottom: 8
      }
    }
  }
});

export default theme
<commit_msg>shade2<commit_after>import createLWTheme from './createThemeDefaults.js';
import grey from '@material-ui/core/colors/grey';
import deepOrange from '@material-ui/core/colors/deepOrange';

const sansSerifStack = [
  'Calibri',
  '"Gill Sans"',
  '"Gill Sans MT"',
  "Myriad Pro",
  'Myriad',
  '"DejaVu Sans Condensed"',
  '"Liberation Sans"',
  '"Nimbus Sans L"',
  'Tahoma',
  'Geneva',
  '"Helvetica Neue"',
  'Helvetica',
  'Arial',
  'sans-serif'
].join(',')

const serifStack = [
  'warnock-pro',
  'Palatino',
  '"Palatino Linotype"',
  '"Palatino LT STD"',
  '"Book Antiqua"',
  'Georgia',
  'serif'
].join(',')

const palette = {
  primary: {
    main: '#5f9b65',
  },
  secondary: {
    main: '#5f9b65',
  },
  lwTertiary: {
    main: "#77947b"
  },
  error: {
    main: deepOrange[900]
  },
  background: {
    default: '#fff'
  }
}

const theme = createLWTheme({
  palette: palette,
  typography: {
    fontFamily: sansSerifStack,
    postStyle: {
      fontFamily: serifStack,
      linkUnderlinePosition: "72%",
    },
    headerStyle: {
      fontFamily: serifStack,
      linkUnderlinePosition: "72%",
    },
    body2: {
      fontSize: "1.16rem"
    },
    commentStyle: {
      fontFamily: sansSerifStack
    },
    headline: {
      fontFamily: serifStack,
    },
    subheading: {
      fontFamily: serifStack,
    },
    title: {
      fontFamily: serifStack,
      fontWeight: 500,
    }
  },
  overrides: {
    MuiAppBar: {
      colorDefault: {
        backgroundColor: grey[50],
      }
    },
    PostsVote: {
      voteScores: {
        margin: "25% 15% 15% 15%"
      }
    },
    MuiTooltip: {
      tooltip: {
        fontSize: "1rem",
        padding: ".7rem",
      }
    },
    MuiDialogContent: {
      root: {
        fontFamily: sansSerifStack,
        fontSize: "1.16rem",
        lineHeight: "1.5em"
      }
    },
    MuiMenuItem: {
      root: {
        fontFamily: sansSerifStack,
        color: grey[800],
        fontSize: "1.1rem",
        lineHeight: "1em"
      }
    },
    MuiListItem: {
      root: {
        paddingTop: 8,
        paddingBottom: 8
      }
    }
  }
});

export default theme
<|endoftext|>


<commit_before>// @flow
import {expect} from 'chai';

import {
    scenario,
    scenarios,
    setupForRspec,
    setupForAva,
    Hidden,
    State,
    Stage,
    Quoted,
    parametrized1,
} from '../src';

import {
    BasicScenarioGivenStage,
    BasicScenarioWhenStage,
    BasicScenarioThenStage,
} from './basic-stages';

if (global.describe && global.it) {
    setupForRspec(describe, it);
} else {
    const test = require('ava');
    setupForAva(test);
}

class ScenarioHiddenStepsGivenStage extends BasicScenarioGivenStage {
    @State scenarioFunc;

    a_scenario_that_includes_an_hidden_step(): this {
        class DefaultStage extends Stage {
            a_visible_step(): this {
                return this;
            }
            @Hidden
            anHiddenStep(): this {
                return this;
            }
        }
        this.scenarioRunner.scenarios(
            'group_name',
            DefaultStage,
            ({given, when, then}) => {
                return {
                    scenario_name: scenario({}, () => {
                        given().a_visible_step().and().anHiddenStep();

                        when();

                        then();
                    }),
                };
            }
        );
        return this;
    }
}

class ScenarioHiddenStepsThenStage extends BasicScenarioThenStage {
    its_given_part_contains_the_steps(expectedSteps: string[]): this {
        const {steps} = this.findPartByKind('GIVEN');
        expect(steps.map(({name}) => name)).to.deep.equal(expectedSteps);
        return this;
    }
}

scenarios(
    'core.scenarios.hidden',
    [
        ScenarioHiddenStepsGivenStage,
        BasicScenarioWhenStage,
        ScenarioHiddenStepsThenStage,
    ],
    ({given, when, then}) => ({
        hidden_steps_are_not_present_in_the_report: scenario({}, () => {
            given()
                .a_scenario_runner()
                .and()
                .a_scenario_that_includes_an_hidden_step();

            when().the_scenario_is_executed();

            then().its_given_part_contains_the_steps(['Given a visible step']);
        }),
    })
);

class HiddenChecksStage extends Stage {
    error: Error;

    @Quoted('value')
    trying_to_build_a_stage_that_uses_an_hidden_decorator_on_a_property_with_value(
        value: mixed
    ): this {
        try {
            // eslint-disable-next-line no-unused-vars
            class AStage extends Stage {
                @Hidden property: mixed = value;
            }
        } catch (error) {
            this.error = error;
        }
        return this;
    }

    @Quoted('value')
    trying_to_build_a_stage_that_declares_an_hidden_step_on_a_property_with_value(
        value: mixed
    ): this {
        try {
            // eslint-disable-next-line no-inner-declarations
            function AStage() {}
            AStage.prototype = {
                property: value,
            };
            Object.setPrototypeOf(AStage.prototype, Stage.prototype);
            Object.setPrototypeOf(AStage, Stage);
            // $FlowIgnore
            Hidden.addHiddenStep(AStage, 'property');
        } catch (error) {
            this.error = error;
        }
        return this;
    }

    an_error_is_thrown_with_the_message(message: string): this {
        expect(this.error).to.exist;
        expect(this.error.message).to.equal(message);
        return this;
    }
}

scenarios(
    'core.scenarios.hidden',
    HiddenChecksStage,
    ({given, when, then}) => ({
        hidden_decorator_cannot_be_used_on_a_property_that_is_not_a_function: scenario(
            {},
            parametrized1([null, undefined, 42, '1337', {}, []], value => {
                when().trying_to_build_a_stage_that_uses_an_hidden_decorator_on_a_property_with_value(
                    value
                );

                then().an_error_is_thrown_with_the_message(
                    "@Hidden decorator can only be applied to methods: 'property' is not a method."
                );
            })
        ),

        hidden_addHiddenStep_cannot_be_used_on_a_property_that_is_not_a_function: scenario(
            {},
            parametrized1([null, undefined, 42, '1337', {}, []], value => {
                when().trying_to_build_a_stage_that_declares_an_hidden_step_on_a_property_with_value(
                    value
                );

                then().an_error_is_thrown_with_the_message(
                    "Hidden.addHiddenStep() can only be applied to methods: 'property' is not a method."
                );
            })
        ),
    })
);
<commit_msg>feat(hidden-steps): Improve scenario<commit_after>// @flow
import {expect} from 'chai';

import {
    scenario,
    scenarios,
    setupForRspec,
    setupForAva,
    Hidden,
    State,
    Stage,
    Quoted,
    parametrized1,
} from '../src';

import {
    BasicScenarioGivenStage,
    BasicScenarioWhenStage,
    BasicScenarioThenStage,
} from './basic-stages';

if (global.describe && global.it) {
    setupForRspec(describe, it);
} else {
    const test = require('ava');
    setupForAva(test);
}

class ScenarioHiddenStepsGivenStage extends BasicScenarioGivenStage {
    @State scenarioFunc;

    a_scenario_that_includes_an_hidden_step(): this {
        class DefaultStage extends Stage {
            a_visible_step(): this {
                return this;
            }
            @Hidden
            anHiddenStep(): this {
                return this;
            }
        }
        this.scenarioRunner.scenarios(
            'group_name',
            DefaultStage,
            ({given, when, then}) => {
                return {
                    scenario_name: scenario({}, () => {
                        given().a_visible_step().and().anHiddenStep();

                        when();

                        then();
                    }),
                };
            }
        );
        return this;
    }
}

class ScenarioHiddenStepsThenStage extends BasicScenarioThenStage {
    its_given_part_contains_the_steps(expectedSteps: string[]): this {
        const {steps} = this.findPartByKind('GIVEN');
        expect(steps.map(({name}) => name)).to.deep.equal(expectedSteps);
        return this;
    }
}

scenarios(
    'core.scenarios.hidden',
    [
        ScenarioHiddenStepsGivenStage,
        BasicScenarioWhenStage,
        ScenarioHiddenStepsThenStage,
    ],
    ({given, when, then}) => ({
        hidden_steps_are_not_present_in_the_report: scenario({}, () => {
            given()
                .a_scenario_runner()
                .and()
                .a_scenario_that_includes_an_hidden_step();

            when().the_scenario_is_executed();

            then().its_given_part_contains_the_steps(['Given a visible step']);
        }),
    })
);

class HiddenChecksStage extends Stage {
    error: Error;

    @Quoted('value')
    trying_to_build_a_stage_that_uses_an_hidden_decorator_on_a_property_with_value(
        value: mixed
    ): this {
        try {
            // eslint-disable-next-line no-unused-vars
            class AStage extends Stage {
                @Hidden property: mixed = value;
            }
        } catch (error) {
            this.error = error;
        }
        return this;
    }

    @Quoted('value')
    trying_to_build_a_stage_that_declares_an_hidden_step_on_a_property_with_value(
        value: mixed
    ): this {
        try {
            // eslint-disable-next-line no-inner-declarations
            function AStage() {}
            AStage.prototype = {
                property: value,
            };
            Object.setPrototypeOf(AStage.prototype, Stage.prototype);
            Object.setPrototypeOf(AStage, Stage);
            // $FlowIgnore
            Hidden.addHiddenStep(AStage, 'property');
        } catch (error) {
            this.error = error;
        }
        return this;
    }

    @Quoted('message')
    an_error_is_thrown_with_the_message(message: string): this {
        expect(this.error).to.exist;
        expect(this.error.message).to.equal(message);
        return this;
    }
}

scenarios(
    'core.scenarios.hidden',
    HiddenChecksStage,
    ({given, when, then}) => ({
        hidden_decorator_cannot_be_used_on_a_property_that_is_not_a_function: scenario(
            {},
            parametrized1([null, undefined, 42, '1337', {}, []], value => {
                when().trying_to_build_a_stage_that_uses_an_hidden_decorator_on_a_property_with_value(
                    value
                );

                then().an_error_is_thrown_with_the_message(
                    "@Hidden decorator can only be applied to methods: 'property' is not a method."
                );
            })
        ),

        hidden_addHiddenStep_cannot_be_used_on_a_property_that_is_not_a_function: scenario(
            {},
            parametrized1([null, undefined, 42, '1337', {}, []], value => {
                when().trying_to_build_a_stage_that_declares_an_hidden_step_on_a_property_with_value(
                    value
                );

                then().an_error_is_thrown_with_the_message(
                    "Hidden.addHiddenStep() can only be applied to methods: 'property' is not a method."
                );
            })
        ),
    })
);
<|endoftext|>


<commit_before> function initAutocomplete() {

     var London = new google.maps.LatLng(51.508742, -0.120850);

     var mapProp = {
         zoom: 11,
         center: London,
         mapTypeId: google.maps.MapTypeId.ROADMAP
     };

     // Get current location
     if (navigator.geolocation) {
         browserSupportFlag = true;
         navigator.geolocation.getCurrentPosition(function(position) {
             initialLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
             map.setCenter(initialLocation);
         }, function() {
             handleNoGeolocation(browserSupportFlag);
         });
     }

     // Browser doesn't support Geolocation
     else {
         browserSupportFlag = false;
         handleNoGeolocation(browserSupportFlag);
     }

     var map = new google.maps.Map(document.getElementById("googleMap"), mapProp);

     function handleNoGeolocation(errorFlag) {
         if (errorFlag === true) {
             alert("Geolocation service failed.");
             initialLocation = London;
         } else {
             alert("Your browser doesn't support geolocation. We've placed you in London.");
             initialLocation = London;
         }
         map.setCenter(initialLocation);
     }

     // fetch JSON
     function fetchJSONFile(path, callback) {
         var httpRequest = new XMLHttpRequest();
         httpRequest.onreadystatechange = function() {
             if (httpRequest.readyState === 4) {
                 if (httpRequest.status === 200) {
                     var data = httpRequest.responseText;
                     if (callback) callback(data);
                 }
             }
         };
         httpRequest.open('GET', path);
         httpRequest.send();
     }

     var markers = [];

     fetchJSONFile('/data', function(data) {
         // do something with your data
         data = JSON.parse(data);

         Object.keys(data).forEach(function(key) {
             // Adding Markers
             var marker = new google.maps.Marker({
                 position: new google.maps.LatLng(Number(data[key].latitude), Number(data[key].longitude))
             });
             markers.push(marker);
             marker.setMap(map);

            // Add Infowindow
             var infowindow = new google.maps.InfoWindow({
                 content: data[key].Name
             });

             marker.addListener('click', function() {
                 infowindow.open(map, marker);
             });
         });
     });

     // Search Box
     var input = document.getElementById('pac-input');
     var searchBox = new google.maps.places.SearchBox(input);
     map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

     map.addListener('bounds_changed', function() {
         searchBox.setBounds(map.getBounds());
     });

     searchBox.addListener('places_changed', function() {
         var places = searchBox.getPlaces();

         if (places.length === 0) {
             return;
         }

         // Clear out the old markers.
         markers.forEach(function(marker) {
             marker.setMap(null);
         });
         markers = [];

         // For each place, get the icon, name and location.
         var bounds = new google.maps.LatLngBounds();
         places.forEach(function(place) {
             var icon = {
                 url: place.icon,
                 size: new google.maps.Size(71, 71),
                 origin: new google.maps.Point(0, 0),
                 anchor: new google.maps.Point(17, 34),
                 scaledSize: new google.maps.Size(25, 25)
             };

             // Create a marker for each place.
             markers.push(new google.maps.Marker({
                 map: map,
                 icon: icon,
                 title: place.name,
                 position: place.geometry.location
             }));

             if (place.geometry.viewport) {
                 // Only geocodes have viewport.
                 bounds.union(place.geometry.viewport);
             } else {
                 bounds.extend(place.geometry.location);
             }
         });
         map.fitBounds(bounds);
     });
    setTimeout(function(){var mc = new MarkerClusterer(map, markers);}, 5000);
 }
<commit_msg>no more setTimeout for cluster pins! :eggplant: #40<commit_after> function initAutocomplete() {

     var London = new google.maps.LatLng(51.508742, -0.120850);

     var mapProp = {
         zoom: 6,
         center: London,
         mapTypeId: google.maps.MapTypeId.ROADMAP
     };

     // Get current location
     if (navigator.geolocation) {
         browserSupportFlag = true;
         navigator.geolocation.getCurrentPosition(function(position) {
             initialLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
             map.setCenter(initialLocation);
         }, function() {
             handleNoGeolocation(browserSupportFlag);
         });
     }

     // Browser doesn't support Geolocation
     else {
         browserSupportFlag = false;
         handleNoGeolocation(browserSupportFlag);
     }

     var map = new google.maps.Map(document.getElementById("googleMap"), mapProp);

     function handleNoGeolocation(errorFlag) {
         if (errorFlag === true) {
             alert("Geolocation service failed.");
             initialLocation = London;
         } else {
             alert("Your browser doesn't support geolocation. We've placed you in London.");
             initialLocation = London;
         }
         map.setCenter(initialLocation);
     }

     // fetch JSON
     function fetchJSONFile(path, callback) {
         var httpRequest = new XMLHttpRequest();
         httpRequest.onreadystatechange = function() {
             if (httpRequest.readyState === 4) {
                 if (httpRequest.status === 200) {
                     var data = httpRequest.responseText;
                     if (callback) callback(data);
                 }
             }
         };
         httpRequest.open('GET', path);
         httpRequest.send();
     }

     var markers = [];

     fetchJSONFile('/data', function(data) {
         // do something with your data
         data = JSON.parse(data);

         Object.keys(data).forEach(function(key) {
             // Adding Markers
             var marker = new google.maps.Marker({
                 position: new google.maps.LatLng(Number(data[key].latitude), Number(data[key].longitude))
             });
             markers.push(marker);
            //  markers.push(index);
             marker.setMap(map);

            // Add Infowindow
             var infowindow = new google.maps.InfoWindow({
                 content: data[key].Name
             });

             marker.addListener('click', function() {
                 infowindow.open(map, marker);
             });
         });
         console.log(markers.length);
         var mc = new MarkerClusterer(map, markers)
     });

     // Search Box
     var input = document.getElementById('pac-input');
     var searchBox = new google.maps.places.SearchBox(input);
     map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

     map.addListener('bounds_changed', function() {
         searchBox.setBounds(map.getBounds());
     });

     searchBox.addListener('places_changed', function() {
         var places = searchBox.getPlaces();

         if (places.length === 0) {
             return;
         }

         // Clear out the old markers.
         markers.forEach(function(marker) {
             marker.setMap(null);
         });
         markers = [];

         // For each place, get the icon, name and location.
         var bounds = new google.maps.LatLngBounds();
         places.forEach(function(place) {
             var icon = {
                 url: place.icon,
                 size: new google.maps.Size(71, 71),
                 origin: new google.maps.Point(0, 0),
                 anchor: new google.maps.Point(17, 34),
                 scaledSize: new google.maps.Size(25, 25)
             };

             // Create a marker for each place.
             markers.push(new google.maps.Marker({
                 map: map,
                 icon: icon,
                 title: place.name,
                 position: place.geometry.location
             }));

             if (place.geometry.viewport) {
                 // Only geocodes have viewport.
                 bounds.union(place.geometry.viewport);
             } else {
                 bounds.extend(place.geometry.location);
             }
         });
         map.fitBounds(bounds);
     });
 }
<|endoftext|>


<commit_before>define(["js/data/RestDataSource"], function (RestDataSource) {

    var SprdApiContext = RestDataSource.RestContext.inherit({
        getQueryParameter: function () {

            var parameter = {
                mediaType: "json"
            };

            if (this.$properties && this.$properties.locale) {
                parameter.locale = this.$properties.locale;
            }

            return rAppid._.defaults(parameter, this.callBase());
        }
    });

    var ShopContext = SprdApiContext.inherit({
        // some sugar for the developer here
        getPathComponents: function () {
            return ['shops', this.$properties.shopId];
        }
    });

    var UserContext = SprdApiContext.inherit({
        // some sugar for the developer here
        getPathComponents: function () {
            return ['users', this.$properties.userId];
        }
    });

    var contextParameterExtractor = /.*(shops|users)\/(\d+)\/.+$/i;

    return RestDataSource.inherit({

        ctor: function () {
            this.callBase();
        },

        defaults: {
            endPoint: "http://api.spreadshirt.net/api/v1",
            gateway: "api",
            apiKey: null,
            secret: null,
            collectionPageSize: 100
        },

        initialize: function () {
            this.callBase();

            if (!this.$.apiKey) {
                console.log("No apiKey for SprdApiDataSource definied");
            }
        },

        createContext: function (properties, parentContext) {

            if (properties) {
                if (properties.shopId) {
                    return new ShopContext(this, properties, parentContext);
                }

                if (properties.userId) {
                    return new UserContext(this, properties, parentContext);
                }
            }

            return new SprdApiContext(this, properties, parentContext);
        },

        createContextCacheId: function (properties, parentProperties) {
            // only use locale from parentProperties
            var parent = {};
            if (parentProperties && parentProperties.locale) {
                parent.locale = parentProperties.locale;
            }

            return this.callBase(properties, parent);
        },

        getContextPropertiesFromReference: function (reference) {

            var match = contextParameterExtractor.exec(reference);
            if (match) {
                if (match[1] == "shops") {
                    return {
                        shopId: match[2]
                    };
                }

                if (match[1] == "users") {
                    return {
                        userId: match[2]
                    }
                }
            }

            return null;
        },

        getQueryParams: function () {
            return rAppid._.defaults({
                mediaType: "json"
            }, this.callBase());
        },

        extractListMetaData: function (list, payload, options) {
            return payload;
        },

        extractListData: function (list, payload, options) {
            for (var key in payload) {
                if (payload.hasOwnProperty(key)) {
                    if (rAppid._.isArray(payload[key])) {
                        return payload[key];
                    }
                }
            }
        },

        shop: function (shopId, locale) {

            shopId = shopId || this.$.shopId;
            locale = locale || this.$.locale;

            var properties = {
                shopId: shopId
            };

            if (locale) {
                properties.locale = locale;
            }

            return this.getContext(properties);
        },
        user: function (userId, locale) {
            userId = userId || this.$.shopId;
            locale = locale || this.$.locale;

            var properties = {
                userId: userId
            };

            if (locale) {
                properties.locale = locale;
            }

            return this.getContext(properties);
        },
        root: function () {
            return this.getContext();
        }
    });
});<commit_msg>fixed underscore dependencies<commit_after>define(["js/data/RestDataSource", "underscore"], function (RestDataSource,_) {

    var SprdApiContext = RestDataSource.RestContext.inherit({
        getQueryParameter: function () {

            var parameter = {
                mediaType: "json"
            };

            if (this.$properties && this.$properties.locale) {
                parameter.locale = this.$properties.locale;
            }

            return _.defaults(parameter, this.callBase());
        }
    });

    var ShopContext = SprdApiContext.inherit({
        // some sugar for the developer here
        getPathComponents: function () {
            return ['shops', this.$properties.shopId];
        }
    });

    var UserContext = SprdApiContext.inherit({
        // some sugar for the developer here
        getPathComponents: function () {
            return ['users', this.$properties.userId];
        }
    });

    var contextParameterExtractor = /.*(shops|users)\/(\d+)\/.+$/i;

    return RestDataSource.inherit({

        ctor: function () {
            this.callBase();
        },

        defaults: {
            endPoint: "http://api.spreadshirt.net/api/v1",
            gateway: "api",
            apiKey: null,
            secret: null,
            collectionPageSize: 100
        },

        initialize: function () {
            this.callBase();

            if (!this.$.apiKey) {
                console.log("No apiKey for SprdApiDataSource definied");
            }
        },

        createContext: function (properties, parentContext) {

            if (properties) {
                if (properties.shopId) {
                    return new ShopContext(this, properties, parentContext);
                }

                if (properties.userId) {
                    return new UserContext(this, properties, parentContext);
                }
            }

            return new SprdApiContext(this, properties, parentContext);
        },

        createContextCacheId: function (properties, parentProperties) {
            // only use locale from parentProperties
            var parent = {};
            if (parentProperties && parentProperties.locale) {
                parent.locale = parentProperties.locale;
            }

            return this.callBase(properties, parent);
        },

        getContextPropertiesFromReference: function (reference) {

            var match = contextParameterExtractor.exec(reference);
            if (match) {
                if (match[1] == "shops") {
                    return {
                        shopId: match[2]
                    };
                }

                if (match[1] == "users") {
                    return {
                        userId: match[2]
                    }
                }
            }

            return null;
        },

        getQueryParams: function () {
            return _.defaults({
                mediaType: "json"
            }, this.callBase());
        },

        extractListMetaData: function (list, payload, options) {
            return payload;
        },

        extractListData: function (list, payload, options) {
            for (var key in payload) {
                if (payload.hasOwnProperty(key)) {
                    if (_.isArray(payload[key])) {
                        return payload[key];
                    }
                }
            }
        },

        shop: function (shopId, locale) {

            shopId = shopId || this.$.shopId;
            locale = locale || this.$.locale;

            var properties = {
                shopId: shopId
            };

            if (locale) {
                properties.locale = locale;
            }

            return this.getContext(properties);
        },
        user: function (userId, locale) {
            userId = userId || this.$.shopId;
            locale = locale || this.$.locale;

            var properties = {
                userId: userId
            };

            if (locale) {
                properties.locale = locale;
            }

            return this.getContext(properties);
        },
        root: function () {
            return this.getContext();
        }
    });
});<|endoftext|>


<commit_before>var _ = require("lodash");
var DB = require("./DB");
var getArg = require("./getArg");
var Future = require("fibers/future");
var modules = require("./modules");
var krl_stdlib = require("krl-stdlib");
var KRLClosure = require("./KRLClosure");
var SymbolTable = require("symbol-table");
var applyInFiber = require("./applyInFiber");
var EventEmitter = require("events");
var evalRuleInFiber = require("./evalRuleInFiber");
var selectRulesToEvalFuture = Future.wrap(require("./selectRulesToEval"));

var mkCTX = function(ctx){
  ctx.getArg = getArg;
  ctx.KRLClosure = KRLClosure;
  if(!_.has(ctx, "emit")){
    ctx.emit = function(){
      console.error("WARNING ctx.emit(...) was not defined!");
    };//stdlib expects an "emit" function to be available
  }
  ctx.callKRLstdlib = function(fn_name){
    var args = _.toArray(arguments);
    args[0] = ctx;
    return krl_stdlib[fn_name].apply(void 0, args);
  };
  return ctx;
};

var rulesets = {};
var salience_graph = {};

var doInstallRuleset = function(rs){
  rs.scope = SymbolTable();
  if(_.isFunction(rs.meta && rs.meta.configure)){
    rs.meta.configure(mkCTX({
      scope: rs.scope
    }));
  }
  if(_.isFunction(rs.global)){
    rs.global(mkCTX({
      scope: rs.scope
    }));
  }
  rs.modules_used = {};
  _.each(rs.meta && rs.meta.use, function(use){
    if(use.kind !== "module"){
      throw new Error("Unsupported 'use' kind: " + use.kind);
    }
    if(!_.has(rulesets, use.rid)){
      throw new Error("Dependant module not loaded: " + use.rid);
    }
    var dep_rs = rulesets[use.rid];
    var ctx = mkCTX({
      scope: SymbolTable()//or dep_rs.scope.push() ??? TODO
    });
    if(_.isFunction(dep_rs.meta && dep_rs.meta.configure)){
      dep_rs.meta.configure(ctx);
    }
    if(_.isFunction(use["with"])){
      use["with"](ctx);
    }
    if(_.isFunction(dep_rs.global)){
      dep_rs.global(ctx);
    }
    rs.modules_used[use.alias] = {
      rid: use.rid,
      scope: ctx.scope,
      provides: dep_rs.meta.provides
    };
  });
  _.each(rs.rules, function(rule){
    rule.rid = rs.rid;

    _.each(rule.select && rule.select.graph, function(g, domain){
      _.each(g, function(exprs, type){
        _.set(salience_graph, [domain, type, rule.rid, rule.name], true);
      });
    });
  });
  rulesets[rs.rid] = rs;
};

var installRuleset = function(rs, callback){
  applyInFiber(doInstallRuleset, null, [rs], callback);
};

module.exports = function(conf){
  var db = Future.wrap(DB(conf.db));
  var compileAndLoadRuleset = conf.compileAndLoadRuleset;

  var emitter = new EventEmitter();

  var installRID = function(rid, callback){
    if(conf._dont_check_enabled_before_installing){//for testing
      compileAndLoadRuleset({rid: rid}, function(err, rs){
        if(err) return callback(err);
        installRuleset(rs, callback);
      });
      return;
    }
    db.getEnableRuleset(rid, function(err, data){
      if(err) return callback(err);
      compileAndLoadRuleset({
        rid: rid,
        src: data.src,
        hash: data.hash
      }, function(err, rs){
        if(err){
          db.disableRuleset(rid, function(){
            callback(err);
          });
          return;
        }
        installRuleset(rs, callback);
      });
    });
  };

  //TODO standard startup-phase
  db.getAllEnableRulesets(function(err, rids){
    if(err){
      throw err;//TODO handle this somehow?
    }
    _.each(rids, function(rid){
      installRID(rid, function(err){
        if(err){
          throw err;//TODO handle this somehow?
        }
      });
    });
  });

  var signalEventInFiber = function(event){
    event.timestamp = new Date();
    event.getAttr = function(attr_key){
      return event.attrs[attr_key];
    };
    event.getAttrMatches = function(pairs){
      var matches = [];
      var i, attr, m, pair;
      for(i = 0; i < pairs.length; i++){
        pair = pairs[i];
        attr = event.attrs[pair[0]];
        m = pair[1].exec(attr || "");
        if(!m){
          return undefined;
        }
        matches.push(m[1]);
      }
      return matches;
    };
    var debug_info = {
      event: {
        eci: event.eci,
        eid: event.eid,
        domain: event.domain,
        type: event.type,
        attrs: _.cloneDeep(event.attrs),
        timestamp: event.timestamp.toISOString()
      }
    };
    emitter.emit("debug", "event", debug_info, "event recieved");

    var pico = db.getPicoByECIFuture(event.eci).wait();
    if(!pico){
      throw new Error("Invalid eci: " + event.eci);
    }
    debug_info.pico_id = pico.id;
    emitter.emit("debug", "event", debug_info, "pico selected");

    var ctx = mkCTX({
      emit: function(type, val, message){//for stdlib
        //TODO think this through more
        emitter.emit(type, debug_info, val, message);
      },
      pico: pico,
      db: db,
      engine: engine,
      modules: modules,
      event: event
    });

    var rules = selectRulesToEvalFuture(ctx, salience_graph, rulesets).wait();
    var responses = _.map(rules, function(rule){

      ctx.emit("debug", "rule selected: " + rule.rid + " -> " + rule.name);

      ctx.rid = rule.rid;
      ctx.rule = rule;
      ctx.scope = rule.scope;
      if(_.has(rulesets, rule.rid)){
        ctx.modules_used = rulesets[rule.rid].modules_used;
      }

      return evalRuleInFiber(rule, ctx);
    });

    var res_by_type = _.groupBy(_.flattenDeep(_.values(responses)), "type");

    //TODO other types
    return {
      directives:  _.map(res_by_type.directive, function(d){
        return _.omit(d, "type");
      })
    };
  };

  var signalEvent = function(event, callback){
    applyInFiber(signalEventInFiber, void 0, [event], callback);
  };

  var engine = Future.wrap({
    installRID: installRID,
    signalEvent: signalEvent
  });

  return {
    db: db,
    emitter: emitter,
    isInstalled: function(rid){
      return _.has(rulesets, rid);
    },
    installRID: installRID,
    signalEvent: signalEvent,
    runQuery: function(query, callback){
      db.getPicoByECI(query.eci, function(err, pico){
        if(err) return callback(err);
        if(!pico){
          return callback(new Error("Bad eci"));
        }
        if(!_.has(pico.ruleset, query.rid)){
          return callback(new Error("Pico does not have that rid"));
        }
        if(!_.has(rulesets, query.rid)){
          return callback(new Error("Not found: rid"));
        }
        var rs = rulesets[query.rid];
        var shares = _.get(rs, ["meta", "shares"]);
        if(!_.isArray(shares) || !_.includes(shares, query.name)){
          return callback(new Error("Not shared"));
        }
        if(!rs.scope.has(query.name)){
          //TODO throw -or- nil????
          return callback(new Error("Shared, but not defined: " + query.name));
        }

        ////////////////////////////////////////////////////////////////////////
        var ctx = mkCTX({
          db: db,
          rid: rs.rid,
          pico: pico,
          emit: function(type, val, message){//for stdlib
            //TODO think this through more
            emitter.emit(type, {rid: rs.rid}, val, message);
          },
          engine: engine,
          modules: modules,
          modules_used: rs.modules_used,
          scope: rs.scope
        });

        var val = ctx.scope.get(query.name);
        if(_.isFunction(val)){
          applyInFiber(val, null, [ctx, query.args], function(err, resp){
            if(err) return callback(err);
            callback(undefined, resp);
          });
        }else{
          callback(undefined, val);
        }
      });
    }
  };
};
<commit_msg>rulesets and salience_graph are now part of the pico-engine closure<commit_after>var _ = require("lodash");
var DB = require("./DB");
var getArg = require("./getArg");
var Future = require("fibers/future");
var modules = require("./modules");
var krl_stdlib = require("krl-stdlib");
var KRLClosure = require("./KRLClosure");
var SymbolTable = require("symbol-table");
var applyInFiber = require("./applyInFiber");
var EventEmitter = require("events");
var evalRuleInFiber = require("./evalRuleInFiber");
var selectRulesToEvalFuture = Future.wrap(require("./selectRulesToEval"));

module.exports = function(conf){
  var db = Future.wrap(DB(conf.db));
  var compileAndLoadRuleset = conf.compileAndLoadRuleset;

  var rulesets = {};
  var salience_graph = {};

  var emitter = new EventEmitter();

  var mkCTX = function(ctx){
    ctx.getArg = getArg;
    ctx.KRLClosure = KRLClosure;
    if(!_.has(ctx, "emit")){
      ctx.emit = function(){
        console.error("WARNING ctx.emit(...) was not defined!");
      };//stdlib expects an "emit" function to be available
    }
    ctx.callKRLstdlib = function(fn_name){
      var args = _.toArray(arguments);
      args[0] = ctx;
      return krl_stdlib[fn_name].apply(void 0, args);
    };
    return ctx;
  };

  var doInstallRuleset = function(rs){
    rs.scope = SymbolTable();
    if(_.isFunction(rs.meta && rs.meta.configure)){
      rs.meta.configure(mkCTX({
        scope: rs.scope
      }));
    }
    if(_.isFunction(rs.global)){
      rs.global(mkCTX({
        scope: rs.scope
      }));
    }
    rs.modules_used = {};
    _.each(rs.meta && rs.meta.use, function(use){
      if(use.kind !== "module"){
        throw new Error("Unsupported 'use' kind: " + use.kind);
      }
      if(!_.has(rulesets, use.rid)){
        throw new Error("Dependant module not loaded: " + use.rid);
      }
      var dep_rs = rulesets[use.rid];
      var ctx = mkCTX({
        scope: SymbolTable()//or dep_rs.scope.push() ??? TODO
      });
      if(_.isFunction(dep_rs.meta && dep_rs.meta.configure)){
        dep_rs.meta.configure(ctx);
      }
      if(_.isFunction(use["with"])){
        use["with"](ctx);
      }
      if(_.isFunction(dep_rs.global)){
        dep_rs.global(ctx);
      }
      rs.modules_used[use.alias] = {
        rid: use.rid,
        scope: ctx.scope,
        provides: dep_rs.meta.provides
      };
    });
    _.each(rs.rules, function(rule){
      rule.rid = rs.rid;

      _.each(rule.select && rule.select.graph, function(g, domain){
        _.each(g, function(exprs, type){
          _.set(salience_graph, [domain, type, rule.rid, rule.name], true);
        });
      });
    });
    rulesets[rs.rid] = rs;
  };

  var installRuleset = function(rs, callback){
    applyInFiber(doInstallRuleset, null, [rs], callback);
  };

  var installRID = function(rid, callback){
    if(conf._dont_check_enabled_before_installing){//for testing
      compileAndLoadRuleset({rid: rid}, function(err, rs){
        if(err) return callback(err);
        installRuleset(rs, callback);
      });
      return;
    }
    db.getEnableRuleset(rid, function(err, data){
      if(err) return callback(err);
      compileAndLoadRuleset({
        rid: rid,
        src: data.src,
        hash: data.hash
      }, function(err, rs){
        if(err){
          db.disableRuleset(rid, function(){
            callback(err);
          });
          return;
        }
        installRuleset(rs, callback);
      });
    });
  };

  //TODO standard startup-phase
  db.getAllEnableRulesets(function(err, rids){
    if(err){
      throw err;//TODO handle this somehow?
    }
    _.each(rids, function(rid){
      installRID(rid, function(err){
        if(err){
          throw err;//TODO handle this somehow?
        }
      });
    });
  });

  var signalEventInFiber = function(event){
    event.timestamp = new Date();
    event.getAttr = function(attr_key){
      return event.attrs[attr_key];
    };
    event.getAttrMatches = function(pairs){
      var matches = [];
      var i, attr, m, pair;
      for(i = 0; i < pairs.length; i++){
        pair = pairs[i];
        attr = event.attrs[pair[0]];
        m = pair[1].exec(attr || "");
        if(!m){
          return undefined;
        }
        matches.push(m[1]);
      }
      return matches;
    };
    var debug_info = {
      event: {
        eci: event.eci,
        eid: event.eid,
        domain: event.domain,
        type: event.type,
        attrs: _.cloneDeep(event.attrs),
        timestamp: event.timestamp.toISOString()
      }
    };
    emitter.emit("debug", "event", debug_info, "event recieved");

    var pico = db.getPicoByECIFuture(event.eci).wait();
    if(!pico){
      throw new Error("Invalid eci: " + event.eci);
    }
    debug_info.pico_id = pico.id;
    emitter.emit("debug", "event", debug_info, "pico selected");

    var ctx = mkCTX({
      emit: function(type, val, message){//for stdlib
        //TODO think this through more
        emitter.emit(type, debug_info, val, message);
      },
      pico: pico,
      db: db,
      engine: engine,
      modules: modules,
      event: event
    });

    var rules = selectRulesToEvalFuture(ctx, salience_graph, rulesets).wait();
    var responses = _.map(rules, function(rule){

      ctx.emit("debug", "rule selected: " + rule.rid + " -> " + rule.name);

      ctx.rid = rule.rid;
      ctx.rule = rule;
      ctx.scope = rule.scope;
      if(_.has(rulesets, rule.rid)){
        ctx.modules_used = rulesets[rule.rid].modules_used;
      }

      return evalRuleInFiber(rule, ctx);
    });

    var res_by_type = _.groupBy(_.flattenDeep(_.values(responses)), "type");

    //TODO other types
    return {
      directives:  _.map(res_by_type.directive, function(d){
        return _.omit(d, "type");
      })
    };
  };

  var signalEvent = function(event, callback){
    applyInFiber(signalEventInFiber, void 0, [event], callback);
  };

  var engine = Future.wrap({
    installRID: installRID,
    signalEvent: signalEvent
  });

  return {
    db: db,
    emitter: emitter,
    isInstalled: function(rid){
      return _.has(rulesets, rid);
    },
    installRID: installRID,
    signalEvent: signalEvent,
    runQuery: function(query, callback){
      db.getPicoByECI(query.eci, function(err, pico){
        if(err) return callback(err);
        if(!pico){
          return callback(new Error("Bad eci"));
        }
        if(!_.has(pico.ruleset, query.rid)){
          return callback(new Error("Pico does not have that rid"));
        }
        if(!_.has(rulesets, query.rid)){
          return callback(new Error("Not found: rid"));
        }
        var rs = rulesets[query.rid];
        var shares = _.get(rs, ["meta", "shares"]);
        if(!_.isArray(shares) || !_.includes(shares, query.name)){
          return callback(new Error("Not shared"));
        }
        if(!rs.scope.has(query.name)){
          //TODO throw -or- nil????
          return callback(new Error("Shared, but not defined: " + query.name));
        }

        ////////////////////////////////////////////////////////////////////////
        var ctx = mkCTX({
          db: db,
          rid: rs.rid,
          pico: pico,
          emit: function(type, val, message){//for stdlib
            //TODO think this through more
            emitter.emit(type, {rid: rs.rid}, val, message);
          },
          engine: engine,
          modules: modules,
          modules_used: rs.modules_used,
          scope: rs.scope
        });

        var val = ctx.scope.get(query.name);
        if(_.isFunction(val)){
          applyInFiber(val, null, [ctx, query.args], function(err, resp){
            if(err) return callback(err);
            callback(undefined, resp);
          });
        }else{
          callback(undefined, val);
        }
      });
    }
  };
};
<|endoftext|>


<commit_before>(function () {
    'use strict';

    /**
     * @param Base
     * @param {$rootScope.Scope} $scope
     * @param {jQuery} $element
     * @param $timeout
     * @param carouselManager
     * @param ChartFactory
     * @param {app.utils} utils
     * @return {Carousel}
     */
    const controller = function ($element, $timeout, carouselManager, utils, Base, $scope, ChartFactory) {

        const { last } = require('ramda');

        const chartOptions = {
            red: {
                charts: [
                    {
                        axisX: 'timestamp',
                        axisY: 'rate',
                        lineColor: '#ef4829',
                        fillColor: '#FFF',
                        gradientColor: ['#FEEFEC', '#FFF'],
                        lineWidth: 4
                    }
                ]
            },
            blue: {
                charts: [
                    {
                        axisX: 'timestamp',
                        axisY: 'rate',
                        lineColor: '#1f5af6',
                        fillColor: '#FFF',
                        gradientColor: ['#EAF0FE', '#FFF'],
                        lineWidth: 4
                    }
                ]
            }
        };

        class Carousel extends Base {

            /**
             * @type {number}
             */
            length = null;
            /**
             * @type {string}
             * @private
             */
            id = null;
            /**
             * @type {number}
             * @private
             */
            interval = null;
            /**
             * @private
             */
            timer = null;
            /**
             * @private
             * @type {jQuery}
             */
            content = null;
            /**
             * @private
             * @type {Array}
             */
            _coords = null;

            $postLink() {
                this.observeOnce('pairsInfoList', () => {
                    utils.postDigest($scope).then(() => {
                        carouselManager.registerSlider(this.id, this);
                        this.interval = Number(this.interval) || 0;
                        this.wrapper = $element.find('.slider-content:first');
                        this.content = $element.find('.slider-content:first').children();
                        this._mapSlides = this.content.toArray().map(slide => ({ $slide: $(slide), translateX: null }));

                        this._fillGraphs();
                        this._remapSlides();

                        const onResize = utils.debounceRequestAnimationFrame(() => this._remapSlides());
                        this.listenEventEmitter($(window), 'resize', onResize);

                        $element.hover(() => {
                            this.stopInterval();
                        }, () => {
                            this.initializeInterval();
                        });
                        this.initializeInterval();
                    });
                });

            }

            $onDestroy() {
                carouselManager.removeSlider(this.id);
            }

            initializeInterval() {
                if (this.interval) {
                    if (this.timer) {
                        this.stopInterval();
                    }
                    this.timer = $timeout(() => this._step(), this.interval);
                }
            }

            stopInterval() {
                if (this.interval && this.timer) {
                    $timeout.cancel(this.timer);
                    this.timer = null;
                }
            }

            /**
             * @public
             */
            goToDexDemo(pairAssets) {
                utils.openDex(pairAssets.assetId1, pairAssets.assetId2, 'dex-demo');
            }

            /**
             * @private
             */
            _fillGraphs() {
                this.content.toArray().forEach((element, i) => {
                    const info = this.pairsInfoList[i];
                    const options = info.change24.gt(0) ? chartOptions.blue : chartOptions.red;
                    new ChartFactory(
                        $(element).find('.graph'),
                        options,
                        this.pairsInfoList[i].rateHistory
                    );
                });
            }

            /**
             * @private
             */
            _remapSlides() {
                this.slidesAmount = this._getSlidesInWindowAmount(window.innerWidth);

                const { width, startCoords } = this._copyStaticSlides();
                this.diff = startCoords.length > 1 ? startCoords[1] - startCoords[0] : width + 10;
                this._coords = this.content.toArray().map((element, i) => (i - 1) * this.diff);

                this._mapSlides.forEach((slide, i) => {
                    slide.translateX = this._coords[i];
                    slide.$slide.css({ transform: `translateX(${this._coords[i]}px)`, width });
                });

                this.content.addClass('absolute');
            }

            /**
             * @private
             */
            _copyStaticSlides() {
                const tempWrapper = this.wrapper.clone();
                tempWrapper.find('.slide').slice(this.slidesAmount).remove();
                const slides = tempWrapper.find('.slide');
                slides
                    .removeAttr('style')
                    .removeClass('absolute');
                tempWrapper.appendTo($element.find('.slider'));

                const width = slides.outerWidth();
                const startCoords = slides
                    .toArray()
                    .map(slide => Math.round($(slide).offset().left));

                tempWrapper.remove();
                return { width, startCoords };
            }

            /**
             * @private
             */
            _getSlidesInWindowAmount(width) {
                switch (true) {
                    case (width < 620):
                        return 1;
                    case (width < 1000):
                        return 2;
                    case (width < 1440):
                        return 3;
                    default:
                        return 4;
                }
            }

            /**
             * @return Promise
             * @private
             */
            _move() {
                const lastPos = last(this._coords);
                return Promise.all(this._mapSlides.map(slide => {
                    const $slide = slide.$slide;
                    const start = slide.translateX;
                    const duration = 1000;
                    const end = start - this.diff;
                    slide.translateX = end;
                    const opacity = this._switchOpacity(end);

                    return utils.animate($slide, {
                        opacity,
                        progress: 1
                    }, {
                        duration: duration,
                        progress: this._animateProgress(start, end, $slide),
                        complete: () => {
                            if (end < -this.diff) {
                                $slide.css('transform', `translateX(${lastPos}px)`);
                                slide.translateX = lastPos;
                            }
                        }
                    });
                }));
            }

            /**
             * @private
             */
            _animateProgress(start, end, slide) {
                return (animation, progress) => {
                    slide.css('transform', `translateX(${start + ((end - start) * progress)}px)`);
                };
            }

            /**
             * @private
             */
            _step() {
                this.stopInterval();
                this._move().then(() => {
                    this.initializeInterval();
                });
            }

            /**
             * @private
             */
            _switchOpacity(newPos) {
                switch (true) {
                    case (newPos < -this.diff || newPos > this._coords[this.slidesAmount + 1]):
                        return 0;
                    case (newPos < 0 || newPos > this._coords[this.slidesAmount]):
                        return 0.2;
                    default:
                        return 1;
                }
            }

        }

        return new Carousel();
    };

    controller.$inject = ['$element', '$timeout', 'carouselManager', 'utils', 'Base', '$scope', 'ChartFactory'];

    angular.module('app.ui').component('wCarousel', {
        transclude: true,
        bindings: {
            id: '@',
            interval: '@',
            pairsInfoList: '<'
        },
        controller: controller,
        templateUrl: 'modules/ui/directives/carousel/carousel.html'
    });
})();
<commit_msg>DEXW-1766:<commit_after>(function () {
    'use strict';

    /**
     * @param Base
     * @param {$rootScope.Scope} $scope
     * @param {jQuery} $element
     * @param $timeout
     * @param carouselManager
     * @param ChartFactory
     * @param {app.utils} utils
     * @return {Carousel}
     */
    const controller = function ($element, $timeout, carouselManager, utils, Base, $scope, ChartFactory) {

        const { last } = require('ramda');

        const chartOptions = {
            red: {
                charts: [
                    {
                        axisX: 'timestamp',
                        axisY: 'rate',
                        lineColor: '#ef4829',
                        fillColor: '#FFF',
                        gradientColor: ['#FEEFEC', '#FFF'],
                        lineWidth: 4
                    }
                ]
            },
            blue: {
                charts: [
                    {
                        axisX: 'timestamp',
                        axisY: 'rate',
                        lineColor: '#1f5af6',
                        fillColor: '#FFF',
                        gradientColor: ['#EAF0FE', '#FFF'],
                        lineWidth: 4
                    }
                ]
            }
        };

        class Carousel extends Base {

            /**
             * @type {number}
             */
            length = null;
            /**
             * @type {string}
             * @private
             */
            id = null;
            /**
             * @type {number}
             * @private
             */
            interval = null;
            /**
             * @private
             */
            timer = null;
            /**
             * @private
             * @type {number}
             */
            _diff = null;
            /**
             * @private
             * @type {jQuery}
             */
            $slides = null;
            /**
             * @private
             * @type {Array}
             */
            _coords = null;

            $postLink() {
                this.observeOnce('pairsInfoList', () => {
                    utils.postDigest($scope).then(() => {
                        carouselManager.registerSlider(this.id, this);
                        this.interval = Number(this.interval) || 0;
                        this.$slidesContainer = $element.find('.slider-content:first');
                        this.$slides = $element.find('.slider-content:first').children();
                        this._mapSlides = this.$slides.toArray().map(slide => ({ $slide: $(slide), translateX: null }));

                        this._fillGraphs();
                        this._remapSlides();

                        const onResize = utils.debounceRequestAnimationFrame(() => this._remapSlides());
                        this.listenEventEmitter($(window), 'resize', onResize);

                        $element.hover(() => {
                            this.stopInterval();
                        }, () => {
                            this.initializeInterval();
                        });
                        this.initializeInterval();
                    });
                });

            }

            $onDestroy() {
                carouselManager.removeSlider(this.id);
            }

            initializeInterval() {
                if (this.interval) {
                    if (this.timer) {
                        this.stopInterval();
                    }
                    this.timer = $timeout(() => this._step(), this.interval);
                }
            }

            stopInterval() {
                if (this.interval && this.timer) {
                    $timeout.cancel(this.timer);
                    this.timer = null;
                }
            }

            /**
             * @public
             */
            goToDexDemo(pairAssets) {
                utils.openDex(pairAssets.assetId1, pairAssets.assetId2, 'dex-demo');
            }

            /**
             * @private
             */
            _fillGraphs() {
                this._mapSlides.forEach(({ $slide }, i) => {
                    const info = this.pairsInfoList[i];
                    const options = info.change24.gt(0) ? chartOptions.blue : chartOptions.red;
                    new ChartFactory(
                        $slide.find('.graph'),
                        options,
                        this.pairsInfoList[i].rateHistory
                    );
                });
            }

            /**
             * @private
             */
            _remapSlides() {
                this.slidesAmount = this._getSlidesInWindowAmount(window.innerWidth);

                const { width, startCoords } = this._copyStaticSlides();
                this._diff = startCoords.length > 1 ? startCoords[1] - startCoords[0] : width + 10;
                this._coords = this.$slides.toArray().map((element, i) => (i - 1) * this._diff);

                this._mapSlides.forEach((slide, i) => {
                    slide.translateX = this._coords[i];
                    slide.$slide.css({ transform: `translateX(${this._coords[i]}px)` });
                });

                this.$slides.addClass('absolute');
            }

            /**
             * @private
             */
            _copyStaticSlides() {
                const temp$slidesContainer = this.$slidesContainer.clone();
                temp$slidesContainer.find('.slide').slice(this.slidesAmount).remove();
                const slides = temp$slidesContainer.find('.slide');
                slides
                    .removeAttr('style')
                    .removeClass('absolute');
                temp$slidesContainer.appendTo($element.find('.slider'));

                const width = slides.outerWidth();
                const startCoords = slides
                    .toArray()
                    .map(slide => Math.round($(slide).offset().left));

                temp$slidesContainer.remove();
                return { width, startCoords };
            }

            /**
             * @private
             */
            _getSlidesInWindowAmount(width) {
                switch (true) {
                    case (width < 620):
                        return 1;
                    case (width < 1000):
                        return 2;
                    case (width < 1440):
                        return 3;
                    default:
                        return 4;
                }
            }

            /**
             * @return Promise
             * @private
             */
            _move() {
                const lastPos = last(this._coords);
                return Promise.all(this._mapSlides.map(slide => {
                    const $slide = slide.$slide;
                    const start = slide.translateX;
                    const duration = 1000;
                    const end = start - this._diff;
                    slide.translateX = end;
                    const opacity = this._switchOpacity(end);

                    return utils.animate($slide, {
                        opacity,
                        progress: 1
                    }, {
                        duration: duration,
                        progress: this._animateProgress(start, end, $slide),
                        complete: () => {
                            if (end < -this._diff) {
                                $slide.css('transform', `translateX(${lastPos}px)`);
                                slide.translateX = lastPos;
                            }
                        }
                    });
                }));
            }

            /**
             * @private
             */
            _animateProgress(start, end, slide) {
                return (animation, progress) => {
                    slide.css('transform', `translateX(${start + ((end - start) * progress)}px)`);
                };
            }

            /**
             * @private
             */
            _step() {
                this.stopInterval();
                this._move().then(() => {
                    this.initializeInterval();
                });
            }

            /**
             * @private
             */
            _switchOpacity(newPos) {
                switch (true) {
                    case (newPos < -this._diff || newPos > this._coords[this.slidesAmount + 1]):
                        return 0;
                    case (newPos < 0 || newPos > this._coords[this.slidesAmount]):
                        return 0.2;
                    default:
                        return 1;
                }
            }

        }

        return new Carousel();
    };

    controller.$inject = ['$element', '$timeout', 'carouselManager', 'utils', 'Base', '$scope', 'ChartFactory'];

    angular.module('app.ui').component('wCarousel', {
        transclude: true,
        bindings: {
            id: '@',
            interval: '@',
            pairsInfoList: '<'
        },
        controller: controller,
        templateUrl: 'modules/ui/directives/carousel/carousel.html'
    });
})();
<|endoftext|>


<commit_before>define([
    'common/html',
    'common/format',
    'common/ui',
], (
    html,
    format,
    UI,
) => {
    'use strict';

    const t = html.tag,
        span = t('span'),
        cssBaseClass = 'kb-job-state';

    /**
     * Convert a job state into a short string to present in the UI
     *
     * @param {string} jobState
     * @returns {string} label
     */

    function jobLabel(jobState) {
        // covers 'does_not_exist' or invalid job states
        let label = 'Job not found';
        switch (jobState) {
            case 'completed':
                label = 'Success';
                break;
            case 'created':
            case 'estimating':
            case 'queued':
                label = 'Queued';
                break;
            case 'running':
                label = 'Running';
                break;
            case 'error':
                label = 'Failed';
                break;
            case 'terminated':
                label = 'Cancelled';
                break;
        }
        return label;
    }

    /**
     * Translate from EE2's job status (or other job state strings interpreted by the app cell)
     * to a presentable string. This returns a span with the text colored and bolded, and the
     * "nice" readable state string.
     *
     * Translated strings = completed, error, terminated, and does_not_exist. Those all get
     * different colors. Other strings are rendered black.
     * @param {string} jobState
     */

    function niceState(jobState) {
        let label,
            color;
        switch (jobState) {
            case 'completed':
                label = 'success';
                color = 'green';
                break;
            case 'error':
                label = 'error';
                color = 'red';
                break;
            case 'terminated':
                label = 'cancellation';
                color = 'orange';
                break;
            case 'does_not_exist':
                label = 'does_not_exist';
                color = 'orange';
                break;
            default:
                label = jobState;
                color = 'black';
        }

        return span({
            class: `${cssBaseClass}__niceState--${jobState}`,
            style: {
                color: color,
                fontWeight: 'bold'
            }
        }, label);
    }

    function jobNotFound() {
        return [
            'This job was not found, or may not have been registered with this Narrative.',
            'You will not be able to inspect the job status or view the job log'
        ];
    }

    function jobStatusUnknown() {
        return ['Determining job state...'];
    }

    /**
     *
     * @param {object}  jobState       - object representing the current job state
     * @param {boolean} includeHistory - whether to generate a single line status (false), or
     *                               to include the history (e.g. how long the job queued for)
     * @returns {array} jobLines       - an array of lines representing the current job status
     */

    function createJobStatusLines(jobState, includeHistory = false) {
        if (!isValidJobState(jobState)) {
            if (jobState && jobState.job_state && jobState.job_state === 'does_not_exist') {
                return jobNotFound();
            }
            return jobStatusUnknown();
        }

        // valid job state objects should have an 'updated' timestamp.
        if (!jobState.updated) {
            return jobStatusUnknown();
        }

        const jobLines = [];

        // Finished status
        if (jobState.finished) {
            // Amount of time it was in the queue
            jobLines.push(queueTime(jobState));

            if (jobState.running) {
                // how long it ran for (if it ran)
                jobLines.push(runTime(jobState));
            }

            jobLines.push(
                'Finished with ' + niceState(jobState.status) + ' at '
                + span({
                    class: 'kb-timestamp'
                }, format.niceTime(jobState.finished))
            );

            if (includeHistory) {
                return jobLines.reverse();
            }
            return [jobLines.pop()];
        }

        // the job is still running
        if (jobState.running) {
            // Amount of time it was in the queue
            jobLines.push(queueTime(jobState));
            // How long it has been running for
            jobLines.push(runTime(jobState));

            if (includeHistory) {
                return jobLines.reverse();
            }
            return [jobLines.pop()];
        }

        // the job is in the queue
        return [queueTime(jobState)];
    }

    function runTime (jobState) {
        if (!jobState.finished) {
            return UI.loading({ color: 'green' })
                + ' Started running job at '
                + span({
                    class: 'kb-timestamp'
                }, format.niceTime(jobState.running))
                + span({ class: 'runClock', dataElement: 'clock' });
        }
        return 'Ran for '
            + span({
                class: 'kb-elapsed-time'
            }, format.niceDuration(jobState.finished - jobState.running));
    }

    function queueTime (jobState) {
        const endTime = jobState.running || jobState.finished;
        if (endTime) {
            return 'Queued for '
            + span({
                class: 'kb-elapsed-time'
            }, format.niceDuration(endTime - jobState.created));
        }
        // job hasn't run or finished (yet)
        const queueString = UI.loading({ color: 'orange' })
            + ' In the queue since '
            + span({
                class: 'kb-timestamp'
            }, format.niceTime(jobState.created));

        if (jobState.position) {
            return queueString + ', currently at position ' + jobState.position;
        }
        return queueString;
    }

    /**
     * A jobState is deemed valid if
     * 1. It's an object (not an array or atomic type)
     * 2. It has a created key
     * 3. It has a job_id key
     * 4. There's others that are necessary, but the top two are sufficient to judge if it's valid
     *    enough and up to date. This function should be updated as necessary.
     *
     * This is intended to be used to make sure that jobStates are of the latest version of the
     * execution engine.
     * @param {object} jobState
     */
    function isValidJobState(jobState) {
        const requiredProperties = ['job_id', 'created'];
        if (jobState !== null && typeof jobState === 'object') {
            let isValid = true;
            requiredProperties.forEach((prop) => {
                if (!(prop in jobState)) {
                    isValid = false;
                }
            });
            return isValid;
        }
        return false;
    }

    return {
        isValidJobState: isValidJobState,
        niceState: niceState,
        jobLabel: jobLabel,
        createJobStatusLines: createJobStatusLines,
    };
});
<commit_msg>PR response: Adding more code comments and compacting some code<commit_after>define([
    'common/html',
    'common/format',
    'common/ui',
], (
    html,
    format,
    UI,
) => {
    'use strict';

    const t = html.tag,
        span = t('span'),
        cssBaseClass = 'kb-job-state',
        jobNotFound = [
            'This job was not found, or may not have been registered with this Narrative.',
            'You will not be able to inspect the job status or view the job log'
        ],
        jobStatusUnknown = ['Determining job state...'];

    /**
     * Convert a job state into a short string to present in the UI
     *
     * @param {string} jobState
     * @returns {string} label
     */

    function jobLabel(jobState) {
        // covers 'does_not_exist' or invalid job states
        let label = 'Job not found';
        switch (jobState) {
            case 'completed':
                label = 'Success';
                break;
            case 'created':
            case 'estimating':
            case 'queued':
                label = 'Queued';
                break;
            case 'running':
                label = 'Running';
                break;
            case 'error':
                label = 'Failed';
                break;
            case 'terminated':
                label = 'Cancelled';
                break;
        }
        return label;
    }

    /**
     * Translate from EE2's job status (or other job state strings interpreted by the app cell)
     * to a presentable string. This returns a span with the text colored and bolded, and the
     * "nice" readable state string.
     *
     * Translated strings = completed, error, terminated, and does_not_exist. Those all get
     * different colors. Other strings are rendered black.
     * @param {string} jobState
     */

    function niceState(jobState) {
        let label,
            color;
        switch (jobState) {
            case 'completed':
                label = 'success';
                color = 'green';
                break;
            case 'error':
                label = 'error';
                color = 'red';
                break;
            case 'terminated':
                label = 'cancellation';
                color = 'orange';
                break;
            case 'does_not_exist':
                label = 'does_not_exist';
                color = 'orange';
                break;
            default:
                label = jobState;
                color = 'black';
        }

        return span({
            class: `${cssBaseClass}__niceState--${jobState}`,
            style: {
                color: color,
                fontWeight: 'bold'
            }
        }, label);
    }

    /**
     * createJobStatusLines
     * Record the current state of the job and its previous states, including time
     * spent in those states.
     *
     * The job execution engine records timestamps for certain job states; in
     * chronological order, these are:
     *      - created
     *      - queued
     *      - running
     *      - finished
     *
     * Go through job statuses in reverse order to get the most recent state,
     * and work backwards from that to generate the previous states.
     *
     * @param {object}  jobState       - object representing the current job state
     * @param {boolean} includeHistory - whether to generate a single line status (false), or
     *                               to include the history (e.g. how long the job queued for)
     *
     * @returns {array} jobLines       - an array of lines representing the current job status
     */

    function createJobStatusLines(jobState, includeHistory = false) {
        if (!isValidJobState(jobState)) {
            // check whether EE2 sent { job_state: 'does_not_exist' }
            if (jobState && jobState.job_state && jobState.job_state === 'does_not_exist') {
                return jobNotFound;
            }
            return jobStatusUnknown;
        }

        // valid job state objects should have an 'updated' timestamp.
        if (!jobState.updated) {
            return jobStatusUnknown;
        }

        const jobLines = [];

        // Finished status
        if (jobState.finished) {
            // Amount of time it was in the queue
            jobLines.push(queueTime(jobState));

            if (jobState.running) {
                // how long it ran for (if it ran)
                jobLines.push(runTime(jobState));
            }

            jobLines.push(
                'Finished with ' + niceState(jobState.status) + ' at '
                + span({
                    class: 'kb-timestamp'
                }, format.niceTime(jobState.finished))
            );

            if (includeHistory) {
                return jobLines.reverse();
            }
            return [jobLines.pop()];
        }

        // the job is still running
        if (jobState.running) {
            // Amount of time it was in the queue
            jobLines.push(queueTime(jobState));
            // How long it has been running for
            jobLines.push(runTime(jobState));

            if (includeHistory) {
                return jobLines.reverse();
            }
            return [jobLines.pop()];
        }

        // the job is in the queue
        return [queueTime(jobState)];
    }

    function runTime (jobState) {
        if (!jobState.finished) {
            return UI.loading({ color: 'green' })
                + ' Started running job at '
                + span({
                    class: 'kb-timestamp'
                }, format.niceTime(jobState.running))
                + span({ class: 'runClock', dataElement: 'clock' });
        }
        return 'Ran for '
            + span({
                class: 'kb-elapsed-time'
            }, format.niceDuration(jobState.finished - jobState.running));
    }

    /**
     * In successful job execution, there should be 'running' and 'finished'
     * timestamps; the queue time is jobState.running - jobState.created.
     *
     * If the job was terminated before it left the queue, there will be no
     * running timestamp, and we use jobState.finished (the termination timestamp)
     * to calculate the queue time.
     *
     * @param {object} jobState
     * @returns {string} queueString - HTML text description of queue time
     */
    function queueTime (jobState) {
        const endTime = jobState.running || jobState.finished;
        if (endTime) {
            return 'Queued for '
            + span({
                class: 'kb-elapsed-time'
            }, format.niceDuration(endTime - jobState.created));
        }
        // job hasn't run or finished (yet)
        const queueString = UI.loading({ color: 'orange' })
            + ' In the queue since '
            + span({
                class: 'kb-timestamp'
            }, format.niceTime(jobState.created));

        if (jobState.position) {
            return queueString + ', currently at position ' + jobState.position;
        }
        return queueString;
    }

    /**
     * A jobState is deemed valid if
     * 1. It's an object (not an array or atomic type)
     * 2. It has a created key
     * 3. It has a job_id key
     * 4. There's others that are necessary, but the top two are sufficient to judge if it's valid
     *    enough and up to date. This function should be updated as necessary.
     *
     * This is intended to be used to make sure that jobStates are of the latest version of the
     * execution engine.
     * @param {object} jobState
     */
    function isValidJobState(jobState) {
        const requiredProperties = ['job_id', 'created'];
        if (jobState !== null && typeof jobState === 'object') {
            return requiredProperties.every(prop => prop in jobState);
        }
        return false;
    }

    return {
        isValidJobState: isValidJobState,
        niceState: niceState,
        jobLabel: jobLabel,
        createJobStatusLines: createJobStatusLines,
    };
});
<|endoftext|>