<commit_before>import React, {useState, useEffect} from 'react';
import {makeStyles} from '@material-ui/core/styles';
import {Typography, Breadcrumbs, Link, Box} from '@material-ui/core';
import Auth from '../auth.js';
import * as api from '../api.js';
import SearchList from './searchlist.js';
import Viewer from './viewer.js';

const useStyles = makeStyles((theme) => ({
  root: {
    background: '#fff',
  },
}));

/**
 * Main page for the app
 * @return {ReactElement}
 */
export default function Main() {
  const classes = useStyles();

  // Declare state variables
  const [, setIsSignedIn] = useState(false);

  /**
   * @typedef {Object} NavigationState
   * @property {any[]} data
   * @property {React.Dispatch<React.SetStateAction<any[]>>} setData
   * @property {boolean} loading
   * @property {React.Dispatch<React.SetStateAction<boolean>>} setLoading
   * @property {any} selected
   * @property {React.Dispatch<any>} setSelected
   */
  /**
   * Function to generate state getters and setters for
   * a list of data, loading state, and selected data
   * @return {NavigationState} Object containing navigation state variables
   */
  const generateNavigationState = () => {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(false);
    const [selected, setSelected] = useState(null);
    return {
      data,
      setData,
      loading,
      setLoading,
      selected,
      setSelected,
    };
  };

  // Navigation state
  const projects = generateNavigationState();
  const locations = generateNavigationState();
  const datasets = generateNavigationState();
  const dicomStores = generateNavigationState();
  const studies = generateNavigationState();
  const series = generateNavigationState();

  /* On mount, check if user is signed in already or not
  by checking for an access token in local storage */
  useEffect(() => {
    // Load GooglAuth library on mount
    Auth.initClient().then(() => {
      // Set up listener to listen to signed in state changes
      Auth.onSignedInChanged((isSignedIn) => {
        setIsSignedIn(isSignedIn);
      });

      // Check if user is already signed in on page load
      const signedIn = Auth.isSignedIn();
      setIsSignedIn(signedIn);

      if (signedIn) {
        loadProjects();
      } else {
        signIn();
      }
    });
  }, []);

  /**
   * Signs the user in with Google
   */
  const signIn = () => {
    Auth.signIn();
  };

  /**
   * Generic flow for populating data into our react component
   * @param {function(): Promise<any>} apiCall Async function to retrieve data
   * @param {function(boolean): any} setLoading Function to set loading state
   * @param {function(any): any} setData Function to set data state
   */
  const loadData = async (apiCall, setLoading, setData) => {
    setLoading(true);
    try {
      const data = await apiCall();
      setData(data);
    } catch (err) {
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  // Use loadData to generate functions for loading all state data
  const loadProjects = async () =>
    loadData(api.fetchProjects, projects.setLoading, projects.setData);

  const loadFilteredProjects = async (searchQuery) =>
    loadData(() => api.fetchProjects(searchQuery),
        projects.setLoading, projects.setData);

  const loadLocations = async (projectId) =>
    loadData(() => api.fetchLocations(projectId),
        locations.setLoading, locations.setData);

  const loadDatasets = async (projectId, location) =>
    loadData(() => api.fetchDatasets(projectId, location),
        datasets.setLoading, datasets.setData);

  const loadDicomStores = async (projectId, location, dataset) =>
    loadData(() => api.fetchDicomStores(projectId, location, dataset),
        dicomStores.setLoading, dicomStores.setData);

  const loadStudies = async (projectId, location, dataset, dicomStore) =>
    loadData(() => api.fetchStudies(projectId, location, dataset, dicomStore),
        studies.setLoading, studies.setData);

  const loadSeries =
      async (projectId, location, dataset, dicomStore, studyId) =>
    loadData(() => api.fetchSeries(projectId, location, dataset, dicomStore, studyId),
        series.setLoading, series.setData);

  // Methods for selecting a list item and loading data for the next list
  /** @param {number} index Index of project in project list */
  const selectProject = (index) => {
    const projectId = projects.data[index];
    projects.setSelected(projectId);
    loadLocations(projectId);
  };

  /** @param {number} index Index of location in location list */
  const selectLocation = (index) => {
    const locationId = locations.data[index];
    locations.setSelected(locationId);
    loadDatasets(projects.selected, locationId);
  };

  /** @param {number} index Index of dataset in dataset list */
  const selectDataset = (index) => {
    const dataset = datasets.data[index];
    datasets.setSelected(dataset);
    loadDicomStores(projects.selected, locations.selected, dataset);
  };

  /** @param {number} index Index of dicom dtore in dicom dtore list */
  const selectDicomStore = (index) => {
    const dicomStore = dicomStores.data[index];
    dicomStores.setSelected(dicomStore);
    loadStudies(projects.selected, locations.selected,
        datasets.selected, dicomStore);
  };

  /** @param {number} index Index of study in study list */
  const selectStudy = (index) => {
    const study = studies.data[index];
    studies.setSelected(study);
    loadSeries(projects.selected, locations.selected, datasets.selected,
        dicomStores.selected, study['0020000D'].Value[0]);
  };

  /** @param {number} index Index of series in series list */
  const selectSeries = (index) => {
    series.setSelected(series.data[index]);
  };

  /**
   * Resets all navigation state after and including the given
   * navigation state value
   * @param {('project'|'location'|'dataset'|
   * 'dicomStore'|'study'|'series')} navigationStr Navigation state to reset
   */
  const resetChainedState = (navigationStr) => {
    switch (navigationStr) {
      case 'project':
        projects.setSelected(null);
        projects.setData([]);
      case 'location':
        locations.setSelected(null);
        locations.setData([]);
      case 'dataset':
        datasets.setSelected(null);
        datasets.setData([]);
      case 'dicomStore':
        dicomStores.setSelected(null);
        dicomStores.setData([]);
      case 'study':
        studies.setSelected(null);
        studies.setData([]);
      case 'series':
        series.setSelected(null);
        series.setData([]);
    }
  };

  /** Clears all state after and including projects and reloads project list */
  const reloadProjects = () => {
    resetChainedState('project');
    loadProjects();
  };

  /** Clears all state after and including locations and reloads project list */
  const reloadLocations = () => {
    resetChainedState('location');
    loadLocations(projects.selected);
  };

  /** Clears all state after and including datasets and reloads dataset list */
  const reloadDatasets = () => {
    resetChainedState('dataset');
    loadDatasets(projects.selected, locations.selected);
  };

  /** Clears all state after and including dicom
   * stores and reloads dicom store list */
  const reloadDicomStores = () => {
    resetChainedState('dicomStore');
    loadDicomStores(projects.selected, locations.selected, datasets.selected);
  };

  /** Clears all state after and including studies and reloads study list */
  const reloadStudies = () => {
    resetChainedState('study');
    loadStudies(projects.selected, locations.selected,
        datasets.selected, dicomStores.selected);
  };

  /** Clears series state and reloads series list */
  const reloadSeries = () => {
    resetChainedState('series');
    loadSeries(projects.selected, locations.selected,
        datasets.selected, dicomStores.selected,
        studies.selected['0020000D'].Value[0]);
  };

  const handleProjectSearch = (searchQuery) => {
    loadFilteredProjects(searchQuery);
  };

  return (

      <Box m={2} display="flex" flexDirection="row">
        <Box flexGrow={1}>
          <Breadcrumbs>
            {projects.selected ?
              <Link color="inherit" href="#" onClick={reloadProjects}>
                {projects.selected}
              </Link> :
              <Typography color="textPrimary">
                Select Project
              </Typography>}
            {locations.selected ?
              <Link color="inherit" href="#" onClick={reloadLocations}>
                {locations.selected}
              </Link> :
              projects.selected ?
                <Typography color="textPrimary">
                  Select Location
                </Typography> : null}
            {datasets.selected ?
              <Link color="inherit" href="#" onClick={reloadDatasets}>
                {datasets.selected}
              </Link> :
              locations.selected ?
                <Typography color="textPrimary">
                  Select Dataset
                </Typography> : null}
            {dicomStores.selected ?
              <Link color="inherit" href="#" onClick={reloadDicomStores}>
                {dicomStores.selected}
              </Link> :
              datasets.selected ?
                <Typography color="textPrimary">
                  Select Dicom Store
                </Typography> : null}
            {studies.selected ?
              <Link color="inherit" href="#" onClick={reloadStudies}>
                {studies.selected['00100020'].Value[0]}
              </Link> :
              dicomStores.selected ?
                <Typography color="textPrimary">
                  Select Study
                </Typography> : null}
            {series.selected ?
              <Link color="inherit" href="#" onClick={reloadSeries}>
                {series.selected['00080060'].Value[0]}
              </Link> :
              studies.selected ?
                <Typography color="textPrimary">
                  Select Series
                </Typography> : null}
          </Breadcrumbs>
        </Box>
      </Box>

      {!projects.selected ?
        <SearchList
          items={projects.data}
          onClickItem={selectProject}
          isLoading={projects.loading}
          onSearch={handleProjectSearch}
          searchDelay={200} /> : null}
      {(projects.selected && !locations.selected) ?
        <SearchList
          items={locations.data}
          onClickItem={selectLocation}
          isLoading={locations.loading} /> : null}
      {(locations.selected && !datasets.selected) ?
        <SearchList
          items={datasets.data}
          onClickItem={selectDataset}
          isLoading={datasets.loading} /> : null}
      {(datasets.selected && !dicomStores.selected) ?
        <SearchList
          items={dicomStores.data}
          onClickItem={selectDicomStore}
          isLoading={dicomStores.loading} /> : null}
      {(dicomStores.selected && !studies.selected) ?
        <SearchList
          items={studies.data.map((study) => study['00100020'].Value[0])}
          onClickItem={selectStudy}
          isLoading={studies.loading} /> : null}
      {(studies.selected && !series.selected) ?
        <SearchList
          items={series.data.map((series) => series['00080060'].Value[0])}
          onClickItem={selectSeries}
          isLoading={series.loading} /> : null}
      {selectedSeries ?
        <Viewer
          project={projects.selected}
          location={locations.selected}
          dataset={datasets.selected}
          dicomStore={dicomStores.selected}
          study={studies.selected}
          series={series.selected} /> : null}

  );
}
<commit_msg>Changed selectedSeries to series.selected<commit_after>import React, {useState, useEffect} from 'react';
import {makeStyles} from '@material-ui/core/styles';
import {Typography, Breadcrumbs, Link, Box} from '@material-ui/core';
import Auth from '../auth.js';
import * as api from '../api.js';
import SearchList from './searchlist.js';
import Viewer from './viewer.js';

const useStyles = makeStyles((theme) => ({
  root: {
    background: '#fff',
  },
}));

/**
 * Main page for the app
 * @return {ReactElement}
 */
export default function Main() {
  const classes = useStyles();

  // Declare state variables
  const [, setIsSignedIn] = useState(false);

  /**
   * @typedef {Object} NavigationState
   * @property {any[]} data
   * @property {React.Dispatch<React.SetStateAction<any[]>>} setData
   * @property {boolean} loading
   * @property {React.Dispatch<React.SetStateAction<boolean>>} setLoading
   * @property {any} selected
   * @property {React.Dispatch<any>} setSelected
   */
  /**
   * Function to generate state getters and setters for
   * a list of data, loading state, and selected data
   * @return {NavigationState} Object containing navigation state variables
   */
  const generateNavigationState = () => {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(false);
    const [selected, setSelected] = useState(null);
    return {
      data,
      setData,
      loading,
      setLoading,
      selected,
      setSelected,
    };
  };

  // Navigation state
  const projects = generateNavigationState();
  const locations = generateNavigationState();
  const datasets = generateNavigationState();
  const dicomStores = generateNavigationState();
  const studies = generateNavigationState();
  const series = generateNavigationState();

  /* On mount, check if user is signed in already or not
  by checking for an access token in local storage */
  useEffect(() => {
    // Load GooglAuth library on mount
    Auth.initClient().then(() => {
      // Set up listener to listen to signed in state changes
      Auth.onSignedInChanged((isSignedIn) => {
        setIsSignedIn(isSignedIn);
      });

      // Check if user is already signed in on page load
      const signedIn = Auth.isSignedIn();
      setIsSignedIn(signedIn);

      if (signedIn) {
        loadProjects();
      } else {
        signIn();
      }
    });
  }, []);

  /**
   * Signs the user in with Google
   */
  const signIn = () => {
    Auth.signIn();
  };

  /**
   * Generic flow for populating data into our react component
   * @param {function(): Promise<any>} apiCall Async function to retrieve data
   * @param {function(boolean): any} setLoading Function to set loading state
   * @param {function(any): any} setData Function to set data state
   */
  const loadData = async (apiCall, setLoading, setData) => {
    setLoading(true);
    try {
      const data = await apiCall();
      setData(data);
    } catch (err) {
      console.error(err);
    } finally {
      setLoading(false);
    }
  };

  // Use loadData to generate functions for loading all state data
  const loadProjects = async () =>
    loadData(api.fetchProjects, projects.setLoading, projects.setData);

  const loadFilteredProjects = async (searchQuery) =>
    loadData(() => api.fetchProjects(searchQuery),
        projects.setLoading, projects.setData);

  const loadLocations = async (projectId) =>
    loadData(() => api.fetchLocations(projectId),
        locations.setLoading, locations.setData);

  const loadDatasets = async (projectId, location) =>
    loadData(() => api.fetchDatasets(projectId, location),
        datasets.setLoading, datasets.setData);

  const loadDicomStores = async (projectId, location, dataset) =>
    loadData(() => api.fetchDicomStores(projectId, location, dataset),
        dicomStores.setLoading, dicomStores.setData);

  const loadStudies = async (projectId, location, dataset, dicomStore) =>
    loadData(() => api.fetchStudies(projectId, location, dataset, dicomStore),
        studies.setLoading, studies.setData);

  const loadSeries =
      async (projectId, location, dataset, dicomStore, studyId) =>
    loadData(() => api.fetchSeries(projectId, location, dataset, dicomStore, studyId),
        series.setLoading, series.setData);

  // Methods for selecting a list item and loading data for the next list
  /** @param {number} index Index of project in project list */
  const selectProject = (index) => {
    const projectId = projects.data[index];
    projects.setSelected(projectId);
    loadLocations(projectId);
  };

  /** @param {number} index Index of location in location list */
  const selectLocation = (index) => {
    const locationId = locations.data[index];
    locations.setSelected(locationId);
    loadDatasets(projects.selected, locationId);
  };

  /** @param {number} index Index of dataset in dataset list */
  const selectDataset = (index) => {
    const dataset = datasets.data[index];
    datasets.setSelected(dataset);
    loadDicomStores(projects.selected, locations.selected, dataset);
  };

  /** @param {number} index Index of dicom dtore in dicom dtore list */
  const selectDicomStore = (index) => {
    const dicomStore = dicomStores.data[index];
    dicomStores.setSelected(dicomStore);
    loadStudies(projects.selected, locations.selected,
        datasets.selected, dicomStore);
  };

  /** @param {number} index Index of study in study list */
  const selectStudy = (index) => {
    const study = studies.data[index];
    studies.setSelected(study);
    loadSeries(projects.selected, locations.selected, datasets.selected,
        dicomStores.selected, study['0020000D'].Value[0]);
  };

  /** @param {number} index Index of series in series list */
  const selectSeries = (index) => {
    series.setSelected(series.data[index]);
  };

  /**
   * Resets all navigation state after and including the given
   * navigation state value
   * @param {('project'|'location'|'dataset'|
   * 'dicomStore'|'study'|'series')} navigationStr Navigation state to reset
   */
  const resetChainedState = (navigationStr) => {
    switch (navigationStr) {
      case 'project':
        projects.setSelected(null);
        projects.setData([]);
      case 'location':
        locations.setSelected(null);
        locations.setData([]);
      case 'dataset':
        datasets.setSelected(null);
        datasets.setData([]);
      case 'dicomStore':
        dicomStores.setSelected(null);
        dicomStores.setData([]);
      case 'study':
        studies.setSelected(null);
        studies.setData([]);
      case 'series':
        series.setSelected(null);
        series.setData([]);
    }
  };

  /** Clears all state after and including projects and reloads project list */
  const reloadProjects = () => {
    resetChainedState('project');
    loadProjects();
  };

  /** Clears all state after and including locations and reloads project list */
  const reloadLocations = () => {
    resetChainedState('location');
    loadLocations(projects.selected);
  };

  /** Clears all state after and including datasets and reloads dataset list */
  const reloadDatasets = () => {
    resetChainedState('dataset');
    loadDatasets(projects.selected, locations.selected);
  };

  /** Clears all state after and including dicom
   * stores and reloads dicom store list */
  const reloadDicomStores = () => {
    resetChainedState('dicomStore');
    loadDicomStores(projects.selected, locations.selected, datasets.selected);
  };

  /** Clears all state after and including studies and reloads study list */
  const reloadStudies = () => {
    resetChainedState('study');
    loadStudies(projects.selected, locations.selected,
        datasets.selected, dicomStores.selected);
  };

  /** Clears series state and reloads series list */
  const reloadSeries = () => {
    resetChainedState('series');
    loadSeries(projects.selected, locations.selected,
        datasets.selected, dicomStores.selected,
        studies.selected['0020000D'].Value[0]);
  };

  const handleProjectSearch = (searchQuery) => {
    loadFilteredProjects(searchQuery);
  };

  return (

      <Box m={2} display="flex" flexDirection="row">
        <Box flexGrow={1}>
          <Breadcrumbs>
            {projects.selected ?
              <Link color="inherit" href="#" onClick={reloadProjects}>
                {projects.selected}
              </Link> :
              <Typography color="textPrimary">
                Select Project
              </Typography>}
            {locations.selected ?
              <Link color="inherit" href="#" onClick={reloadLocations}>
                {locations.selected}
              </Link> :
              projects.selected ?
                <Typography color="textPrimary">
                  Select Location
                </Typography> : null}
            {datasets.selected ?
              <Link color="inherit" href="#" onClick={reloadDatasets}>
                {datasets.selected}
              </Link> :
              locations.selected ?
                <Typography color="textPrimary">
                  Select Dataset
                </Typography> : null}
            {dicomStores.selected ?
              <Link color="inherit" href="#" onClick={reloadDicomStores}>
                {dicomStores.selected}
              </Link> :
              datasets.selected ?
                <Typography color="textPrimary">
                  Select Dicom Store
                </Typography> : null}
            {studies.selected ?
              <Link color="inherit" href="#" onClick={reloadStudies}>
                {studies.selected['00100020'].Value[0]}
              </Link> :
              dicomStores.selected ?
                <Typography color="textPrimary">
                  Select Study
                </Typography> : null}
            {series.selected ?
              <Link color="inherit" href="#" onClick={reloadSeries}>
                {series.selected['00080060'].Value[0]}
              </Link> :
              studies.selected ?
                <Typography color="textPrimary">
                  Select Series
                </Typography> : null}
          </Breadcrumbs>
        </Box>
      </Box>

      {!projects.selected ?
        <SearchList
          items={projects.data}
          onClickItem={selectProject}
          isLoading={projects.loading}
          onSearch={handleProjectSearch}
          searchDelay={200} /> : null}
      {(projects.selected && !locations.selected) ?
        <SearchList
          items={locations.data}
          onClickItem={selectLocation}
          isLoading={locations.loading} /> : null}
      {(locations.selected && !datasets.selected) ?
        <SearchList
          items={datasets.data}
          onClickItem={selectDataset}
          isLoading={datasets.loading} /> : null}
      {(datasets.selected && !dicomStores.selected) ?
        <SearchList
          items={dicomStores.data}
          onClickItem={selectDicomStore}
          isLoading={dicomStores.loading} /> : null}
      {(dicomStores.selected && !studies.selected) ?
        <SearchList
          items={studies.data.map((study) => study['00100020'].Value[0])}
          onClickItem={selectStudy}
          isLoading={studies.loading} /> : null}
      {(studies.selected && !series.selected) ?
        <SearchList
          items={series.data.map((series) => series['00080060'].Value[0])}
          onClickItem={selectSeries}
          isLoading={series.loading} /> : null}
      {series.selected ?
        <Viewer
          project={projects.selected}
          location={locations.selected}
          dataset={datasets.selected}
          dicomStore={dicomStores.selected}
          study={studies.selected}
          series={series.selected} /> : null}

  );
}
<|endoftext|>


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

import React, {Component, createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import query from 'dom-helpers/query';

import TriggerPop from '../_TriggerPop';

import Theme from '../Theme';
import Position from '../_statics/Position';

import Event from '../_vendors/Event';
import Dom from '../_vendors/Dom';
import Util from '../_vendors/Util';
import PopManagement from '../_vendors/PopManagement';

class Popup extends Component {

    static Position = Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.closeTimeout = null;
        this.pop = createRef();
        this.popInstance = null;

    }

    /**
     * public
     */
    getEl = () => {
        return this.popInstance && this.popInstance.getEl();
    };

    /**
     * public
     */
    resetPosition = () => {
        this.popInstance && this.popInstance.resetPosition();
    };

    clearCloseTimeout = () => {
        if (this.closeTimeout) {
            clearTimeout(this.closeTimeout);
            this.closeTimeout = null;
        }
    };

    triggerHandler = (el, triggerEl, popupEl, currentVisible, isBlurClose) => {

        // el is missing
        if (el && !query.contains(document, el)) {
            return currentVisible;
        }

        if ((triggerEl && el && query.contains(triggerEl, el))
            || (popupEl && el && query.contains(popupEl, el))) {
            return currentVisible;
        }

        return isBlurClose ? false : currentVisible;

    };

    handleClose = e => {

        const {visible, triggerEl, isBlurClose, triggerHandler, onRequestClose} = this.props,
            popupEl = this.popInstance.getEl();

        if (!visible || !triggerEl) {
            return;
        }

        let currVisible;

        if (triggerHandler) {
            currVisible = triggerHandler(e.target, triggerEl, popupEl, visible, isBlurClose);
        } else if (!Dom.isParent(e.target, triggerEl)) {
            currVisible = this.triggerHandler(e.target, triggerEl, popupEl, visible, isBlurClose);
        }

        if (currVisible === false) {
            this.clearCloseTimeout();
            this.closeTimeout = setTimeout(() => {
                onRequestClose && onRequestClose(e);
            });
        }

    };

    handleRender = (...args) => {

        PopManagement.push(this);

        const {onRender} = this.props;
        onRender && onRender(...args);

    };

    handleDestroy = (...args) => {

        PopManagement.pop(this);

        const {onDestroy} = this.props;
        onDestroy && onDestroy(...args);

    };

    componentDidMount() {

        this.popInstance = this.pop && this.pop.current;

        Event.addEvent(document, 'click', this.handleClose);

    }

    componentWillUnmount() {

        this.clearCloseTimeout();
        Event.removeEvent(document, 'click', this.handleClose);

        PopManagement.pop(this);

    }

    render() {

        const {

            className, contentClassName,

            // not passing down these props
            triggerHandler, onRequestClose,

            ...restProps

        } = this.props;

        return (
            <TriggerPop {...restProps}
                        ref={this.pop}
                        className={classNames('popup', {
                            [className]: className
                        })}
                        contentClassName={classNames('popup-content', {
                            [contentClassName]: contentClassName
                        })}
                        onRender={this.handleRender}
                        onDestroy={this.handleDestroy}/>
        );
    }

}

Popup.propTypes = {

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

    /**
     * The CSS class name of the content element.
     */
    contentClassName: PropTypes.string,

    modalClassName: PropTypes.string,

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

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    parentEl: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the popover.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the popover is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popup alignment.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * If true,popup will have animation effects.
     */
    isAnimated: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isBlurClose: PropTypes.bool,
    isEscClose: PropTypes.bool,

    shouldFollowScroll: PropTypes.bool,
    scrollEl: PropTypes.object,

    resetPositionWait: PropTypes.number,
    showModal: PropTypes.bool,

    /**
     * The function of popup event handler.
     */
    triggerHandler: PropTypes.func,

    /**
     * The function of popup render.
     */
    onRender: PropTypes.func,

    /**
     * The function of popup rendered.
     */
    onRendered: PropTypes.func,

    /**
     * The function of popup destroy.
     */
    onDestroy: PropTypes.func,

    /**
     * The function of popup destroyed.
     */
    onDestroyed: PropTypes.func,

    /**
     * Callback function fired when the popover is requested to be closed.
     */
    onRequestClose: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

Popup.defaultProps = {

    parentEl: document.body,

    visible: false,
    hasTriangle: true,
    position: Position.BOTTOM,
    isAnimated: true,

    isBlurClose: true,
    isEscClose: true,
    shouldFollowScroll: false,
    resetPositionWait: 250,
    showModal: false

};

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

import React, {Component, createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import queryContains from 'dom-helpers/query/contains';

// Components
import TriggerPop from '../_TriggerPop';

// Statics
import Theme from '../Theme';
import Position from '../_statics/Position';

// Vendors
import Event from '../_vendors/Event';
import Dom from '../_vendors/Dom';
import Util from '../_vendors/Util';
import PopManagement from '../_vendors/PopManagement';

class Popup extends Component {

    static Position = Position;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.closeTimeout = null;
        this.pop = createRef();
        this.popInstance = null;

    }

    /**
     * public
     */
    getEl = () => {
        return this.popInstance && this.popInstance.getEl();
    };

    /**
     * public
     */
    resetPosition = () => {
        this.popInstance && this.popInstance.resetPosition();
    };

    clearCloseTimeout = () => {
        if (this.closeTimeout) {
            clearTimeout(this.closeTimeout);
            this.closeTimeout = null;
        }
    };

    triggerHandler = (el, triggerEl, popupEl, currentVisible, isBlurClose) => {

        // el is missing
        if (el && !queryContains(document, el)) {
            return currentVisible;
        }

        if ((triggerEl && el && queryContains(triggerEl, el))
            || (popupEl && el && queryContains(popupEl, el))) {
            return currentVisible;
        }

        return isBlurClose ? false : currentVisible;

    };

    handleClose = e => {

        const {visible, triggerEl, isBlurClose, triggerHandler, onRequestClose} = this.props,
            popupEl = this.popInstance.getEl();

        if (!visible || !triggerEl) {
            return;
        }

        let currVisible;

        if (triggerHandler) {
            currVisible = triggerHandler(e.target, triggerEl, popupEl, visible, isBlurClose);
        } else if (!Dom.isParent(e.target, triggerEl)) {
            currVisible = this.triggerHandler(e.target, triggerEl, popupEl, visible, isBlurClose);
        }

        if (currVisible === false) {
            this.clearCloseTimeout();
            this.closeTimeout = setTimeout(() => {
                onRequestClose && onRequestClose(e);
            });
        }

    };

    handleRender = (...args) => {

        PopManagement.push(this);

        const {onRender} = this.props;
        onRender && onRender(...args);

    };

    handleDestroy = (...args) => {

        PopManagement.pop(this);

        const {onDestroy} = this.props;
        onDestroy && onDestroy(...args);

    };

    componentDidMount() {

        this.popInstance = this.pop && this.pop.current;

        Event.addEvent(document, 'click', this.handleClose);

    }

    componentWillUnmount() {

        this.clearCloseTimeout();
        Event.removeEvent(document, 'click', this.handleClose);

        PopManagement.pop(this);

    }

    render() {

        const {

            className, contentClassName,

            // not passing down these props
            triggerHandler, onRequestClose,

            ...restProps

        } = this.props;

        return (
            <TriggerPop {...restProps}
                        ref={this.pop}
                        className={classNames('popup', {
                            [className]: className
                        })}
                        contentClassName={classNames('popup-content', {
                            [contentClassName]: contentClassName
                        })}
                        onRender={this.handleRender}
                        onDestroy={this.handleDestroy}/>
        );
    }

}

Popup.propTypes = {

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

    /**
     * The CSS class name of the content element.
     */
    contentClassName: PropTypes.string,

    modalClassName: PropTypes.string,

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

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    parentEl: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the popover.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the popover is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popup alignment.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * If true,popup will have animation effects.
     */
    isAnimated: PropTypes.bool,

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    isBlurClose: PropTypes.bool,
    isEscClose: PropTypes.bool,

    shouldFollowScroll: PropTypes.bool,
    scrollEl: PropTypes.object,

    resetPositionWait: PropTypes.number,
    showModal: PropTypes.bool,

    /**
     * The function of popup event handler.
     */
    triggerHandler: PropTypes.func,

    /**
     * The function of popup render.
     */
    onRender: PropTypes.func,

    /**
     * The function of popup rendered.
     */
    onRendered: PropTypes.func,

    /**
     * The function of popup destroy.
     */
    onDestroy: PropTypes.func,

    /**
     * The function of popup destroyed.
     */
    onDestroyed: PropTypes.func,

    /**
     * Callback function fired when the popover is requested to be closed.
     */
    onRequestClose: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

Popup.defaultProps = {

    parentEl: document.body,

    visible: false,
    hasTriangle: true,
    position: Position.BOTTOM,
    isAnimated: true,

    isBlurClose: true,
    isEscClose: true,
    shouldFollowScroll: false,
    resetPositionWait: 250,
    showModal: false

};

export default Popup;
<|endoftext|>


<commit_before>import './Popup.js';
import { merge } from './updates.js';
import * as symbols from './symbols.js';
import KeyboardMixin from './KeyboardMixin.js';
import OpenCloseMixin from './OpenCloseMixin.js';
import ReactiveElement from './ReactiveElement.js';

const Base =
  KeyboardMixin(
  OpenCloseMixin(
    ReactiveElement
  ));

class PopupSource extends Base {

  componentDidMount() {
    if (super.componentDidMount) { super.componentDidMount(); }
    this.$.button.addEventListener('mousedown', event => {
      if (!this.opened) {
        setTimeout(() => {
          this[symbols.raiseChangeEvents] = true;
          this.open()
          this[symbols.raiseChangeEvents] = false;
        });
        event.stopPropagation();
        event.preventDefault();
      }
    });
    this.$.popup.addEventListener('opened-changed', event => {
      /** @type {any} */
      const cast = event;
      const opened = cast.detail.opened;
      if (opened !== this.opened) {
        this[symbols.raiseChangeEvents] = true;
        this.opened = opened;
        this[symbols.raiseChangeEvents] = false;
      }
    });
  }

  get defaultState() {
    return Object.assign({}, super.defaultState, {
      horizontalAlign: 'left',
      preferredPosition: 'below'
    });
  }

  // TODO: 'start', 'end'
  get horizontalAlign() {
    return this.state.horizontalAlign;
  }
  set horizontalAlign(horizontalAlign) {
    this.setState({
      horizontalAlign
    });
  }

  [symbols.keydown](event) {
    let handled;
    switch (event.key) {
      // Pressing Enter or Space opens the popup.
      case 'Enter':
      case ' ':
        this.open();
        handled = true;
        break;
    }

    // Prefer mixin result if it's defined, otherwise use base result.
    return handled || (super[symbols.keydown] && super[symbols.keydown](event));
  }

  get preferredPosition() {
    return this.state.preferredPosition;
  }
  set preferredPosition(preferredPosition) {
    this.setState({
      preferredPosition
    });
  }

  // TODO: Tags for popup and source
  get [symbols.template]() {
    return `

      <button id="button" tabindex="-1">
        <slot></slot>
      </button>

        <elix-popup id="popup">
          <slot name="popup"></slot>
        </elix-popup>

    `;
  }

  // TODO: Pressed state for button
  get updates() {

    const buttonStyle = {
      'background-color': this.state.opened ? 'highlight' : ''
    };

    const positionBelow = this.state.preferredPosition === 'below';

    const popupContainerStyle = positionBelow ?
      {
        bottom: '',
        position: 'relative',
        top: ''
      } :
      {
        bottom: 0,
        position: 'absolute',
        top: 0
      };

    const popupStyle = positionBelow ?
      {
        bottom: ''
      } :
      {
        bottom: 0
      };

    const horizontalAlign = this.state.horizontalAlign;
    popupStyle.left = horizontalAlign === 'left' || horizontalAlign === 'stretch' ?
      '0' :
      '';
    popupStyle.right = horizontalAlign === 'right' || horizontalAlign === 'stretch' ?
      '0' :
      '';

    return merge(super.updates, {
      $: {
        button: {
          style: buttonStyle
        },
        popup: {
          opened: this.state.opened,
          style: popupStyle
        },
        popupContainer: {
          style: popupContainerStyle
        }
      }
    });
  }

}

export default PopupSource;
customElements.define('elix-popup-source', PopupSource);
<commit_msg>Only handle primary button mouse down to avoid interfering with right-click behavior.<commit_after>import './Popup.js';
import { merge } from './updates.js';
import * as symbols from './symbols.js';
import KeyboardMixin from './KeyboardMixin.js';
import OpenCloseMixin from './OpenCloseMixin.js';
import ReactiveElement from './ReactiveElement.js';

const Base =
  KeyboardMixin(
  OpenCloseMixin(
    ReactiveElement
  ));

class PopupSource extends Base {

  componentDidMount() {
    if (super.componentDidMount) { super.componentDidMount(); }
    this.$.button.addEventListener('mousedown', event => {
      // Only handle primary button mouse down to avoid interfering with
      // right-click behavior.
      /** @type {any} */
      const cast = event;
      if (cast.button === 0 && !this.opened) {
        setTimeout(() => {
          this[symbols.raiseChangeEvents] = true;
          this.open()
          this[symbols.raiseChangeEvents] = false;
        });
        event.stopPropagation();
        event.preventDefault();
      }
    });
    this.$.popup.addEventListener('opened-changed', event => {
      /** @type {any} */
      const cast = event;
      const opened = cast.detail.opened;
      if (opened !== this.opened) {
        this[symbols.raiseChangeEvents] = true;
        this.opened = opened;
        this[symbols.raiseChangeEvents] = false;
      }
    });
  }

  componentDidUpdate(previousState) {
    if (super.componentDidUpdate) { super.componentDidUpdate(previousState); }

  }

  get defaultState() {
    return Object.assign({}, super.defaultState, {
      horizontalAlign: 'left',
      preferredPosition: 'below'
    });
  }

  // TODO: 'start', 'end'
  get horizontalAlign() {
    return this.state.horizontalAlign;
  }
  set horizontalAlign(horizontalAlign) {
    this.setState({
      horizontalAlign
    });
  }

  [symbols.keydown](event) {
    let handled;
    switch (event.key) {
      // Pressing Enter or Space opens the popup.
      case 'Enter':
      case ' ':
        this.open();
        handled = true;
        break;
    }

    // Prefer mixin result if it's defined, otherwise use base result.
    return handled || (super[symbols.keydown] && super[symbols.keydown](event));
  }

  get preferredPosition() {
    return this.state.preferredPosition;
  }
  set preferredPosition(preferredPosition) {
    this.setState({
      preferredPosition
    });
  }

  // TODO: Tags for popup and source
  get [symbols.template]() {
    return `

      <button id="button" tabindex="-1">
        <slot></slot>
      </button>

        <elix-popup id="popup">
          <slot name="popup"></slot>
        </elix-popup>

    `;
  }

  // TODO: Pressed state for button
  get updates() {

    const buttonStyle = {
      'background-color': this.state.opened ? 'highlight' : ''
    };

    const preferPositionBelow = this.state.preferredPosition === 'below';
    // const fitsBelow = popupFitsBelow();
    // const fitsAbove = popupFitsAbove();
    // const positionBelow = preferPositionBelow && (fitsBelow || !fitsAbove) ||
    //   !preferPositionBelow && !fitsAbove && fitsBelow;
    const positionBelow = preferPositionBelow;

    const popupContainerStyle = positionBelow ?
      {
        bottom: '',
        position: 'relative',
        top: ''
      } :
      {
        bottom: 0,
        position: 'absolute',
        top: 0
      };

    const popupStyle = positionBelow ?
      {
        bottom: ''
      } :
      {
        bottom: 0
      };

    const horizontalAlign = this.state.horizontalAlign;
    popupStyle.left = horizontalAlign === 'left' || horizontalAlign === 'stretch' ?
      '0' :
      '';
    popupStyle.right = horizontalAlign === 'right' || horizontalAlign === 'stretch' ?
      '0' :
      '';

    return merge(super.updates, {
      $: {
        button: {
          style: buttonStyle
        },
        popup: {
          opened: this.state.opened,
          style: popupStyle
        },
        popupContainer: {
          style: popupContainerStyle
        }
      }
    });
  }

}

export default PopupSource;
customElements.define('elix-popup-source', PopupSource);
<|endoftext|>


<commit_before>/*global brackets, define */

define(function (require, exports, module) {
    "use strict";

    var _                   = brackets.getModule("thirdparty/lodash"),
        PreferencesManager  = brackets.getModule("preferences/PreferencesManager"),
        prefs               = PreferencesManager.getExtensionPrefs("brackets-git");

    var defaultPreferences = {
        // features
        "stripWhitespaceFromCommits": {     "type": "boolean",           "value": true          },
        "addEndlineToTheEndOfFile": {       "type": "boolean",           "value": true          },
        "useGitGutter": {                   "type": "boolean",           "value": true          },
        "markModifiedInTree": {             "type": "boolean",           "value": true          },
        "useCodeInspection": {              "type": "boolean",           "value": true          },
        "storePlainTextPasswords": {        "type": "boolean",           "value": false         },
        // shortcuts
        "panelShortcut": {                  "type": "string",            "value": "Ctrl-Alt-G"  },
        "commitCurrentShortcut": {          "type": "string",            "value": null          },
        "commitAllShortcut": {              "type": "string",            "value": null          },
        // system
        "lastVersion": {                    "type": "string",            "value": null          },
        "panelEnabled": {                   "type": "boolean",           "value": true          },
        "TIMEOUT_VALUE": {                  "type": "number",            "value": 30000         },
        "terminalCommand": {                "type": "string",            "value": null          },
        "extensionDirectory": {             "type": "string",            "value": undefined     },
        // platform specific
        "gitIsInSystemPath": {
            "type": "boolean",
            "os": {
                "win":      { "value": true },
                "mac":      { "value": false },
                "linux":    { "value": false }
            }
        },
        "gitPath": {
            "type": "string",
            "os": {
                "win":      { "value": "C:\\Program Files (x86)\\Git\\bin\\git.exe" },
                "mac":      { "value": "/usr/local/git/bin/git" },
                "linux":    { "value": "/usr/bin/git" }
            }
        },
        "msysgitPath": {
            "type": "string",
            "os": {
                "win":      { "value": "C:\\Program Files (x86)\\Git\\" },
                "mac":      { "value": null },
                "linux":    { "value": null }
            }
        }
    };

    _.each(defaultPreferences, function (definition, key) {
        if (definition.os && definition.os[brackets.platform]) {
            prefs.definePreference(key, definition.type, definition.os[brackets.platform].value);
        } else {
            prefs.definePreference(key, definition.type, definition.value);
        }
    });
    prefs.save();

    prefs.getAll = function () {
        var obj = {};
        _.each(defaultPreferences, function (definition, key) {
            obj[key] = this.get(key);
        }, this);
        return obj;
    };

    prefs.getDefaults = function () {
        var obj = {};
        _.each(defaultPreferences, function (definition, key) {
            var defaultValue;
            if (definition.os && definition.os[brackets.platform]) {
                defaultValue = definition.os[brackets.platform].value;
            } else {
                defaultValue = definition.value;
            }
            obj[key] = defaultValue;
        }, this);
        return obj;
    };

    prefs.persist = function (key, value) {
        this.set(key, value);
        this.save();
    };

    module.exports = prefs;
});
<commit_msg>Fix #155<commit_after>/*global brackets, define */

define(function (require, exports, module) {
    "use strict";

    var _                   = brackets.getModule("thirdparty/lodash"),
        PreferencesManager  = brackets.getModule("preferences/PreferencesManager"),
        prefs               = PreferencesManager.getExtensionPrefs("brackets-git");

    var defaultPreferences = {
        // features
        "stripWhitespaceFromCommits": {     "type": "boolean",           "value": true          },
        "addEndlineToTheEndOfFile": {       "type": "boolean",           "value": true          },
        "useGitGutter": {                   "type": "boolean",           "value": true          },
        "markModifiedInTree": {             "type": "boolean",           "value": true          },
        "useCodeInspection": {              "type": "boolean",           "value": true          },
        "storePlainTextPasswords": {        "type": "boolean",           "value": false         },
        // shortcuts
        "panelShortcut": {                  "type": "string",            "value": "Ctrl-Alt-G"  },
        "commitCurrentShortcut": {          "type": "string",            "value": null          },
        "commitAllShortcut": {              "type": "string",            "value": null          },
        // system
        "lastVersion": {                    "type": "string",            "value": null          },
        "panelEnabled": {                   "type": "boolean",           "value": true          },
        "TIMEOUT_VALUE": {                  "type": "number",            "value": 30000         },
        "terminalCommand": {                "type": "string",            "value": null          },
        "extensionDirectory": {             "type": "string",            "value": undefined     },
        "gitIsInSystemPath": {              "type": "boolean",           "value": false         },
        // platform specific
        "gitPath": {
            "type": "string",
            "os": {
                "win":      { "value": "C:\\Program Files (x86)\\Git\\bin\\git.exe" },
                "mac":      { "value": "/usr/local/git/bin/git" },
                "linux":    { "value": "/usr/bin/git" }
            }
        },
        "msysgitPath": {
            "type": "string",
            "os": {
                "win":      { "value": "C:\\Program Files (x86)\\Git\\" },
                "mac":      { "value": null },
                "linux":    { "value": null }
            }
        }
    };

    _.each(defaultPreferences, function (definition, key) {
        if (definition.os && definition.os[brackets.platform]) {
            prefs.definePreference(key, definition.type, definition.os[brackets.platform].value);
        } else {
            prefs.definePreference(key, definition.type, definition.value);
        }
    });
    prefs.save();

    prefs.getAll = function () {
        var obj = {};
        _.each(defaultPreferences, function (definition, key) {
            obj[key] = this.get(key);
        }, this);
        return obj;
    };

    prefs.getDefaults = function () {
        var obj = {};
        _.each(defaultPreferences, function (definition, key) {
            var defaultValue;
            if (definition.os && definition.os[brackets.platform]) {
                defaultValue = definition.os[brackets.platform].value;
            } else {
                defaultValue = definition.value;
            }
            obj[key] = defaultValue;
        }, this);
        return obj;
    };

    prefs.persist = function (key, value) {
        this.set(key, value);
        this.save();
    };

    module.exports = prefs;
});
<|endoftext|>


<commit_before>/*    Copyright 2021 Firewalla Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const flowTool = require('../net2/FlowTool');
const log = require('../net2/logger.js')(__filename)
const _ = require('lodash');
const Sensor = require('./Sensor.js').Sensor
const fc = require('../net2/config.js');
const featureName = 'compress_flows'
const Promise = require('bluebird');
const zlib = require('zlib');
const extensionManager = require('./ExtensionManager.js')
const { rclient } = require('../util/redis_manager');
const deflateAsync = Promise.promisify(zlib.deflate);

class FlowCompressionSensor extends Sensor {
  constructor() {
    super()
    this.recentlyTickKey = "compressed:flows:lastest:ts"
    this.step = 60 * 60 // one hour
    this.maxInterval = 24 * 60 * 60 // 24 hours
  }

  async run() {
    await this.build()
    setInterval(async () => {
      await this.build()
    }, 30 * 60 * 1000);
  }

  async apiRun() {
    extensionManager.onGet("compressedflows", (msg, data) => {
      return this.loadCompressedFlows(data)
    });
  }

  async loadCompressedFlows(options) {
    // options {begin,end}
    let { begin, end } = options
    begin = begin - begin % this.step
    end = end - end % this.step
    log.info(`Load compressed flows between ${new Date(begin * 1000)} - ${new Date(end * 1000)}`)
    const compressedFlows = []
    for (let i = 0; i < (end - begin) / this.step; i++) {
      const beginTs = begin + this.step * i
      const endTs = begin + this.step * (i + 1)
      const str = await rclient.getAsync(this.getKey(beginTs, endTs))
      str && compressedFlows.push(str)
    }
    return compressedFlows
  }

  getKey(begin, end) {
    return `compressed:flows:${begin}:${end}`
  }

  async sensorCheck() {
    if (!fc.isFeatureOn(featureName)) {
      log.info(`${featureName} feature is disabled.`)
      return false
    }
    const members = await rclient.smembersAsync("sys:ept:members")
    if (members && members.length > 0) {
      const webEnabled = members.map((m) => {
        try {
          return JSON.parse(m)
        } catch (err) {
          return null
        }
      }).filter((x) => x != null && x.name == 'firewalla_web').length > 0
      if (!webEnabled) {
        log.info(`firewalla_web haven't enabled`)
      }
      return webEnabled
    }
    return false
  }

  async build() {
    try {
      if (! await this.sensorCheck()) return
      const { begin, end } = await this.getBuildingWindow()
      log.info(`Going to compress flows between ${new Date(begin * 1000)} - ${new Date(end * 1000)}`)
      for (let i = 0; i < (end - begin) / this.step; i++) {
        const beginTs = begin + this.step * i
        const endTs = begin + this.step * (i + 1)
        const flows = await this.loadFlows(beginTs, endTs)
        await this.save(beginTs, endTs, flows)
      }
      await rclient.setAsync(this.recentlyTickKey, end)
    } catch (e) {
      log.error(`Compress flows error`, e)
    }
  }

  async save(begin, end, flows) { // might save to disk in future
    const base64Str = await this.compress(flows)
    const key = this.getKey(begin, end)
    await rclient.setAsync(key, base64Str)
    await rclient.expireatAsync(key, end + this.maxInterval)
  }

  async getBuildingWindow() {
    const now = new Date() / 1000
    const nowTickTs = now - now % this.step
    let recentlyTickTs = Number(await rclient.getAsync(this.recentlyTickKey) || 0)
    if (nowTickTs - recentlyTickTs > this.maxInterval) {
      recentlyTickTs = nowTickTs - this.maxInterval
    }
    return {
      begin: recentlyTickTs,
      end: nowTickTs
    }
  }

  async loadFlows(begin, end) {
    log.info(`Going to load flows between ${new Date(begin * 1000)} - ${new Date(end * 1000)}`)
    let completed = false
    const options = {
      begin: begin,
      end: end,
      audit: true,
      count: 2000,
      asc: true
    }
    let allFlows = []
    const now = new Date() / 1000
    while (!completed) {
      try {
        const flows = await flowTool.prepareRecentFlows({}, JSON.parse(JSON.stringify(options))) || []
        if (flows.length < options.count) {
          completed = true
        } else {
          options.begin = flows[flows.length - 1].ts
        }
        allFlows = allFlows.concat(flows)
      } catch (e) {
        log.error(`Load flows error`, e)
        completed = true
      }
    }
    log.debug(`get ${allFlows.length} flows cost ${(new Date() / 1000 - now).toFixed(2)} seconds`)
    // debug purpose
    log.debug(`there are ${allFlows.reduce((ac, val) => ac + val.count, 0)} zeek logs for these flows`)
    return allFlows
  }

  mergeFlows(flows) {
    if (!flows || flows.length == 0) return [];
    let stash = flows[0];
    const mergedFlows = [stash];
    const compareKeys = ["ltype", "fd", "device", "protocol", "host", "ip", "domain"]
    for (var i = 1; i < flows.length; i++) {
      const flow = flows[i]
      if (_.isEqual(_.pick(stash, compareKeys), _.pick(flow, compareKeys))) {
        stash.count += flow.count
        stash.download += flow.download
        stash.upload += flow.upload
        stash.duration += flow.duration
      } else {
        stash = flow
        mergedFlows.push(stash)
      }
    }
    return mergedFlows
  }
  async compress(flows) {
    const mergedFlows = this.mergeFlows(flows)
    const str = JSON.stringify(mergedFlows)
    const deflateBuffer = await deflateAsync(str)
    const base64Str = deflateBuffer.toString('base64')
    log.debug(`Compress ${mergedFlows.length} flows, raw: ${str.length} deflate: ${deflateBuffer.length} base64:${base64Str.length}`)
    return base64Str
  }
}

module.exports = FlowCompressionSensor;<commit_msg>add compress info log<commit_after>/*    Copyright 2021 Firewalla Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const flowTool = require('../net2/FlowTool');
const log = require('../net2/logger.js')(__filename)
const _ = require('lodash');
const Sensor = require('./Sensor.js').Sensor
const fc = require('../net2/config.js');
const featureName = 'compress_flows'
const Promise = require('bluebird');
const zlib = require('zlib');
const extensionManager = require('./ExtensionManager.js')
const { rclient } = require('../util/redis_manager');
const deflateAsync = Promise.promisify(zlib.deflate);

class FlowCompressionSensor extends Sensor {
  constructor() {
    super()
    this.recentlyTickKey = "compressed:flows:lastest:ts"
    this.step = 60 * 60 // one hour
    this.maxInterval = 24 * 60 * 60 // 24 hours
  }

  async run() {
    setTimeout(async () => {
      await this.build()
    }, 10 * 1000) // first time in 10s
    setInterval(async () => {
      await this.build()
    }, 30 * 60 * 1000);
  }

  async apiRun() {
    extensionManager.onGet("compressedflows", (msg, data) => {
      return this.loadCompressedFlows(data)
    });
  }

  async loadCompressedFlows(options) {
    // options {begin,end}
    let { begin, end } = options
    begin = begin - begin % this.step
    end = end - end % this.step
    log.info(`Load compressed flows between ${new Date(begin * 1000)} - ${new Date(end * 1000)}`)
    const compressedFlows = []
    for (let i = 0; i < (end - begin) / this.step; i++) {
      const beginTs = begin + this.step * i
      const endTs = begin + this.step * (i + 1)
      const str = await rclient.getAsync(this.getKey(beginTs, endTs))
      str && compressedFlows.push(str)
    }
    return compressedFlows
  }

  getKey(begin, end) {
    return `compressed:flows:${begin}:${end}`
  }

  async sensorCheck() {
    if (!fc.isFeatureOn(featureName)) {
      log.info(`${featureName} feature is disabled.`)
      return false
    }
    const members = await rclient.smembersAsync("sys:ept:members")
    if (members && members.length > 0) {
      const webEnabled = members.map((m) => {
        try {
          return JSON.parse(m)
        } catch (err) {
          return null
        }
      }).filter((x) => x != null && x.name == 'firewalla_web').length > 0
      if (!webEnabled) {
        log.info(`firewalla_web haven't enabled`)
      }
      return webEnabled
    }
    return false
  }

  async build() {
    try {
      if (! await this.sensorCheck()) return
      const { begin, end } = await this.getBuildingWindow()
      const now = new Date() / 1000
      log.info(`Going to compress flows between ${new Date(begin * 1000)} - ${new Date(end * 1000)}`)
      for (let i = 0; i < (end - begin) / this.step; i++) {
        const beginTs = begin + this.step * i
        const endTs = begin + this.step * (i + 1)
        const flows = await this.loadFlows(beginTs, endTs)
        await this.save(beginTs, endTs, flows)
      }
      await rclient.setAsync(this.recentlyTickKey, end)
      log.info(`Compressed flows build complted, cost ${(new Date() / 1000 - now).toFixed(2)}`)
    } catch (e) {
      log.error(`Compress flows error`, e)
    }
  }

  async save(begin, end, flows) { // might save to disk in future
    const base64Str = await this.compress(flows)
    const key = this.getKey(begin, end)
    await rclient.setAsync(key, base64Str)
    await rclient.expireatAsync(key, end + this.maxInterval)
  }

  async getBuildingWindow() {
    const now = new Date() / 1000
    const nowTickTs = now - now % this.step
    let recentlyTickTs = Number(await rclient.getAsync(this.recentlyTickKey) || 0)
    if (nowTickTs - recentlyTickTs > this.maxInterval) {
      recentlyTickTs = nowTickTs - this.maxInterval
    }
    return {
      begin: recentlyTickTs,
      end: nowTickTs
    }
  }

  async loadFlows(begin, end) {
    log.info(`Going to load flows between ${new Date(begin * 1000)} - ${new Date(end * 1000)}`)
    let completed = false
    const options = {
      begin: begin,
      end: end,
      audit: true,
      count: 2000,
      asc: true
    }
    let allFlows = []
    const now = new Date() / 1000
    while (!completed) {
      try {
        const flows = await flowTool.prepareRecentFlows({}, JSON.parse(JSON.stringify(options))) || []
        if (flows.length < options.count) {
          completed = true
        } else {
          options.begin = flows[flows.length - 1].ts
        }
        allFlows = allFlows.concat(flows)
      } catch (e) {
        log.error(`Load flows error`, e)
        completed = true
      }
    }
    log.debug(`get ${allFlows.length} flows cost ${(new Date() / 1000 - now).toFixed(2)} seconds`)
    // debug purpose
    log.debug(`there are ${allFlows.reduce((ac, val) => ac + val.count, 0)} zeek logs for these flows`)
    return allFlows
  }

  mergeFlows(flows) {
    if (!flows || flows.length == 0) return [];
    let stash = flows[0];
    const mergedFlows = [stash];
    const compareKeys = ["ltype", "fd", "device", "protocol", "host", "ip", "domain"]
    for (var i = 1; i < flows.length; i++) {
      const flow = flows[i]
      if (_.isEqual(_.pick(stash, compareKeys), _.pick(flow, compareKeys))) {
        stash.count += flow.count
        stash.download += flow.download
        stash.upload += flow.upload
        stash.duration += flow.duration
      } else {
        stash = flow
        mergedFlows.push(stash)
      }
    }
    return mergedFlows
  }
  async compress(flows) {
    const mergedFlows = this.mergeFlows(flows)
    const str = JSON.stringify(mergedFlows)
    const deflateBuffer = await deflateAsync(str)
    const base64Str = deflateBuffer.toString('base64')
    log.debug(`Compress ${mergedFlows.length} flows, raw: ${str.length} deflate: ${deflateBuffer.length} base64:${base64Str.length}`)
    return base64Str
  }
}

module.exports = FlowCompressionSensor;<|endoftext|>


<commit_before>/* eslint-disable no-param-reassign */
/* eslint-disable no-underscore-dangle */

const Document = require('../models/document');

module.exports = {
  create: (req, res) => {
    const document = new Document();
    document.title = req.body.title;
    document.content = req.body.content;
    document.owner = req.decoded._id;

    document.save((err) => {
      if (err) {
        if (err.code === 11000) {
          res.status(409).send({ error: 'Duplicate entry.' });
        } else {
          res.status(400).send({ error: 'Error creating document.' });
        }
      } else {
        res.status(201).send({
          message: 'Document created successfully.',
          document,
        });
      }
    });
  },

  all: (req, res) => {
    Document.find({}, (err, documents) => {
      if (err) {
        res.status(400).send({ error: 'Could not fetch documents.' });
      } else if (documents.length === 0) {
        res.status(404).send({ error: 'No documents to retrieve.' });
      } else {
        res.status(200).send(documents);
      }
    });
  },

  find: (req, res) => {
    Document.findById(req.params.id, (err, document) => {
      if (err || document === null) {
        res.status(404).send({ error: 'Could not find document.' });
      } else {
        res.status(200).send(document);
      }
    });
  },

  update: (req, res) => {
    Document.findById(req.params.id, (err, document) => {
      if (err || document === null) {
        res.status(404).send({ error: 'Document not found.' });
        return;
      }
      Object.keys(req.body).forEach((key) => {
        document[key] = req.body[key];
      });

      document.save((error) => {
        if (error) {
          if (error.code === 11000) {
            res.status(409).send({ error: 'Duplicate entry.' });
            return;
          }
        } else if (Object.keys(req.body).length === 0) {
          res.status(400).send({ error: 'Nothing to update.' });
        } else {
          res.status(200).send({ message: 'Document updated successfully.' });
        }
      });
    });
  },

  delete: (req, res) => {
    Document.findById(req.params.id, (err, document) => {
      if (err || document === null) {
        res.status(404).send({ error: 'Document not found.' });
        return;
      }
      document.remove((error) => {
        if (error) {
          res.status(400).send({ error: 'Could not delete document.' });
        } else {
          res.status(200).send({ message: 'Document deleted successfully.' });
        }
      });
    });
  },
};
<commit_msg>Implement sort-by-date, limit and offset on documents<commit_after>/* eslint-disable no-param-reassign */
/* eslint-disable no-underscore-dangle */

const Document = require('../models/document');

module.exports = {
  create: (req, res) => {
    const document = new Document();
    document.title = req.body.title;
    document.content = req.body.content;
    document.owner = req.decoded._id;

    document.save((err) => {
      if (err) {
        if (err.code === 11000) {
          res.status(409).send({ error: 'Duplicate entry.' });
        } else {
          res.status(400).send({ error: 'Error creating document.' });
        }
      } else {
        res.status(201).send({
          message: 'Document created successfully.',
          document,
        });
      }
    });
  },

  all: (req, res) => {
    Document
    .find({})
    .skip(parseInt(req.query.offset, 10))
    .limit(parseInt(req.query.limit, 10))
    .sort({ createdAt: -1 })
    .exec((err, documents) => {
      if (err) {
        res.status(400).send({ error: 'Could not fetch documents.' });
      } else if (documents.length === 0) {
        res.status(404).send({ error: 'No documents to retrieve.' });
      } else {
        res.status(200).send(documents);
      }
    });
  },

  find: (req, res) => {
    Document.findById(req.params.id, (err, document) => {
      if (err || document === null) {
        res.status(404).send({ error: 'Could not find document.' });
      } else {
        res.status(200).send(document);
      }
    });
  },

  update: (req, res) => {
    Document.findById(req.params.id, (err, document) => {
      if (err || document === null) {
        res.status(404).send({ error: 'Document not found.' });
        return;
      }
      Object.keys(req.body).forEach((key) => {
        document[key] = req.body[key];
      });

      document.save((error) => {
        if (error) {
          if (error.code === 11000) {
            res.status(409).send({ error: 'Duplicate entry.' });
            return;
          }
        } else if (Object.keys(req.body).length === 0) {
          res.status(400).send({ error: 'Nothing to update.' });
        } else {
          res.status(200).send({ message: 'Document updated successfully.' });
        }
      });
    });
  },

  delete: (req, res) => {
    Document.findById(req.params.id, (err, document) => {
      if (err || document === null) {
        res.status(404).send({ error: 'Document not found.' });
        return;
      }
      document.remove((error) => {
        if (error) {
          res.status(400).send({ error: 'Could not delete document.' });
        } else {
          res.status(200).send({ message: 'Document deleted successfully.' });
        }
      });
    });
  },
};
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "menus", "commands", "tabManager", "settings",
        "preferences", "save", "ui"
    ];
    main.provides = ["ace.stripws"];
    return main;

    function main(options, imports, register) {
        var Plugin   = imports.Plugin;
        var menus    = imports.menus;
        var commands = imports.commands;
        var tabs     = imports.tabManager;
        var settings = imports.settings;
        var prefs    = imports.preferences;
        var save     = imports.save;
        var ui       = imports.ui;

        var whitespaceUtil = require("ace/ext/whitespace");

        /***** Initialization *****/

        var plugin = new Plugin("Ajax.org", main.consumes);
        // var emit   = plugin.getEmitter();

        var disabled = false;

        var loaded = false;
        function load(){
            if (loaded) return false;
            loaded = true;

            commands.addCommand({
                name        : "stripws",
                hint        : "strip whitespace at the end of each line",
                exec        : function(){
                    stripws();
                },
                isAvailable : function (editor){
                    return editor && tabs.focussedTab &&
                        typeof tabs.focussedTab.path == "string";
                }
            }, plugin);

            menus.addItemByPath("Tools/Strip Trailing Space", new ui.item({
                command : "stripws"
            }), 100, plugin);

            menus.addItemByPath("Tools/~", new ui.divider(), 200, plugin);

            save.on("beforeSave", function (e) {
                var shouldStrip = settings.getBool("user/general/@stripws");
                if (!shouldStrip)
                    return;
                stripws(e.document.tab);
            }, plugin);

            settings.on("read", function(e) {
                settings.setDefaults("user/general", [["stripws", "false"]]);
            }, plugin);

            prefs.add({
               "General" : {
                    position : 100,
                    "General" : {
                        "On Save, Strip Whitespace" : {
                            type     : "checkbox",
                            position : 900,
                            path     : "user/general/@stripws"
                        }
                    }
               }
            }, plugin);
        }

        /***** Methods *****/

        function stripws(tab) {
            tab = tab || tabs.focussedTab;
            if (!tab || !tab.path || disabled)
                return;

            var session = tab.document.getSession().session;
            whitespaceUtil.trimTrailingSpace(session, true);
            session.$syncInformUndoManager();
        }

        /***** Lifecycle *****/

        plugin.on("load", function(){
            load();
        });
        plugin.on("enable", function(){
            disabled = false;
        });
        plugin.on("disable", function(){
            disabled = true;
        });
        plugin.on("unload", function(){
            loaded = false;
        });

        /***** Register and define API *****/

        /**
         * Strips trailing whitespace from lines just before a file is saved.
         * @singleton
         **/
        plugin.freezePublicAPI({
            /*
             * Strips whitespace at the end of each line in the given page
             * @param {Tab} tab The tab to strip the whitespace from
             * If not provided, the currently focussed tab will be used instead
             */
            strpws: stripws
        });

        register(null, {
            "ace.stripws": plugin
        });
    }
});

<commit_msg>Fixes +1752. More user friendly settings<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "menus", "commands", "tabManager", "settings",
        "preferences", "save", "ui"
    ];
    main.provides = ["ace.stripws"];
    return main;

    function main(options, imports, register) {
        var Plugin   = imports.Plugin;
        var menus    = imports.menus;
        var commands = imports.commands;
        var tabs     = imports.tabManager;
        var settings = imports.settings;
        var prefs    = imports.preferences;
        var save     = imports.save;
        var ui       = imports.ui;

        var whitespaceUtil = require("ace/ext/whitespace");

        /***** Initialization *****/

        var plugin = new Plugin("Ajax.org", main.consumes);
        // var emit   = plugin.getEmitter();

        var disabled = false;

        var loaded = false;
        function load(){
            if (loaded) return false;
            loaded = true;

            commands.addCommand({
                name        : "stripws",
                hint        : "strip whitespace at the end of each line",
                exec        : function(){
                    stripws();
                },
                isAvailable : function (editor){
                    return editor && tabs.focussedTab &&
                        typeof tabs.focussedTab.path == "string";
                }
            }, plugin);

            menus.addItemByPath("Tools/Strip Trailing Space", new ui.item({
                command : "stripws"
            }), 100, plugin);

            menus.addItemByPath("Tools/~", new ui.divider(), 200, plugin);

            save.on("beforeSave", function (e) {
                var shouldStrip = settings.getBool("user/general/@stripws");
                if (!shouldStrip)
                    return;
                stripws(e.document.tab);
            }, plugin);

            settings.on("read", function(e) {
                settings.setDefaults("user/general", [["stripws", "false"]]);
            }, plugin);

            prefs.add({
               "File" : {
                    position : 150,
                    "Whitespace" : {
                        position : 500,
                        "On Save, Strip Whitespace" : {
                            type     : "checkbox",
                            position : 900,
                            path     : "user/general/@stripws"
                        }
                    }
               }
            }, plugin);
        }

        /***** Methods *****/

        function stripws(tab) {
            tab = tab || tabs.focussedTab;
            if (!tab || !tab.path || disabled)
                return;

            var session = tab.document.getSession().session;
            whitespaceUtil.trimTrailingSpace(session, true);
            session.$syncInformUndoManager();
        }

        /***** Lifecycle *****/

        plugin.on("load", function(){
            load();
        });
        plugin.on("enable", function(){
            disabled = false;
        });
        plugin.on("disable", function(){
            disabled = true;
        });
        plugin.on("unload", function(){
            loaded = false;
        });

        /***** Register and define API *****/

        /**
         * Strips trailing whitespace from lines just before a file is saved.
         * @singleton
         **/
        plugin.freezePublicAPI({
            /*
             * Strips whitespace at the end of each line in the given page
             * @param {Tab} tab The tab to strip the whitespace from
             * If not provided, the currently focussed tab will be used instead
             */
            strpws: stripws
        });

        register(null, {
            "ace.stripws": plugin
        });
    }
});

<|endoftext|>


<commit_before>import validator from 'validator';

/**
 * @description Contains all Document Related Logic
 * @export
 * @class Utils
 */
export default class DocumentsLogic {
  /**
   * @description checks if Title exists
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document The returned document
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static titleExist(request, response, document) {
    if (document) {
      response.status(400).send({
        message: 'Title already exists',
      });
      return true;
    }
    return false;
  }

  /**
   * @description adds more properties to the query
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {integer} limit Number of rows to be returned
   * @param {integer} offset Number of rows to be skipped
   * @param {object} property query property
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static listQuery(request, response, limit, offset, property) {
    if (((limit && !(validator.isNumeric(limit)))
      && (offset && !(validator.isNumeric(offset))))
      || ((limit && limit <= 0) && (offset && offset < 0))) {
      response.status(400).send({
        message: 'Invalid Limit and Offset',
      });
      return false;
    } else if ((limit && !(validator.isNumeric(limit))) ||
    (limit && limit <= 0)) {
      response.status(400).send({
        message: 'Invalid Limit',
      });
      return false;
    } else if ((offset && !(validator.isNumeric(offset))) ||
    (offset && offset < 0)) {
      response.status(400).send({
        message: 'Invalid Offset',
      });
      return false;
    }
    property.limit = limit || 10;
    property.offset = offset || 0;
    // if (request.loggedInUser.roleId === 3) {
    //   property.where = { userId: request.loggedInUser.id };
    // }
    response.property = property;
    return true;
  }

  /**
   * @description Checks if document exists
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document the returned document
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static isDocument(request, response, document) {
    if (!document) {
      response.status(404).send({
        message: 'Document Not Found'
      });
      return false;
    }
    return true;
  }

  /**
   * @description Checks Authorization for viewing
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document the returned document
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static isAllowed(request, response, document) {
    const allowed = [document.User.roleId, 1, 2];
    if ((document.access === 'Private' &&
      document.userId !== request.loggedInUser.id) ||
    (document.access === 'Role' && allowed.includes(request.loggedInUser.roleId)
    === false)) {
      response.status(403).send({
        message: 'You are not authorized to view this document'
      });
      return false;
    }
    return true;
  }

  /**
   * @description Checks Authorization for updating
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {integer} ownerId id of the document owner
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static allowUpdate(request, response, ownerId) {
    if (request.loggedInUser.roleId !== 1 &&
      request.loggedInUser.id !== parseInt(ownerId)) {
      response.status(403).send({
        message: 'You cannot update someone else\'s document',
      });
      return false;
    }
    return true;
  }

  /**
   * @description Checks Validity of parameters
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static isValidParams(request, response) {
    if (!request.body.title && validator.isEmpty(request.body.title)) {
      response.status(400).send({
        message: 'Title is Required',
      });
      return false;
    }
    if (!request.body.content && validator.isEmpty(request.body.content)) {
      response.status(400).send({
        message: 'Content is Required',
      });
      return false;
    }
    if (!request.body.access && validator.isEmpty(request.body.access)) {
      response.status(400).send({
        message: 'Access is Required',
      });
      return false;
    }
    if (!(['Public', 'Private', 'Role'].includes(request.body.access))) {
      response.status(400).send({
        message: 'Invalid Access Type',
      });
      return false;
    }
    return true;
  }

  /**
   * @description checks validation error
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} error server error
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static validationError(request, response, error) {
    if (error.toString() ===
      'SequelizeUniqueConstraintError: Validation error') {
      response.status(400).send({
        message: 'Your Edited Title already exists!!!',
      });
      return true;
    }
    return false;
  }

  /**
   * @description Checks Authorization for deleting
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {integer} ownerId id of the document owner
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static allowDelete(request, response, ownerId) {
    if (request.loggedInUser.roleId !== 1 &&
      request.loggedInUser.id !== parseInt(ownerId)) {
      response.status(403).send({
        message: 'You cannot delete someone else\'s document',
      });
      return false;
    }
    return true;
  }

  /**
   * @description Validates document ID for retrieval
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {any} documentId Id of the document to be retrieved
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static documentIdValid(request, response, documentId) {
    if (!validator.isNumeric(documentId)) {
      response.status(400).send({
        message: 'Document Id must be an integer',
      });
      return false;
    }
    return true;
  }

  /**
   * @description Filters Search Result
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document the returned document
   * @returns {boolean} false
   * @memberof Utils
   */
  static filter(request, response, document) {
    let count = 0;
    const newArr = [];
    while (count < document.length) {
      const allowed = [document[count].dataValues.User.dataValues.roleId, 1, 2];
      if (!((document[count].dataValues.access === 'Private'
      && document[count].dataValues.userId !== request.loggedInUser.id) ||
      (document[count].dataValues.access === 'Role'
      && allowed.includes(request.loggedInUser.roleId)
      === false))) {
        delete document[count].dataValues.User;
        newArr.push(document[count]);
      }
      count += 1;
    }
    if (newArr.length === 0) {
      response.status(404).send({ message: 'No Document Found' });
      return false;
    }
    response.document = newArr;
    return false;
  }

  /**
   * @description Paginates Result
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} object the returned document or user
   * @memberof Utils
   */
  static paginate(request, response, object) {
    const totalCount = object.length;
    let pageCount = Math.round(totalCount / (request.query.limit || 10));
    pageCount = (pageCount < 1 && totalCount > 0) ? 1 : pageCount;
    const page = Math.round((request.query.offset || 0) /
    (request.query.limit || 10)) + 1;
    response.status(200).send({ object,
      metaData: {
        page,
        pageCount,
        count: object.length,
        totalCount,
      }
    });
  }
}
<commit_msg>feature(feedback): edit filter function to fix pagination issue<commit_after>import validator from 'validator';

/**
 * @description Contains all Document Related Logic
 * @export
 * @class Utils
 */
export default class DocumentsLogic {
  /**
   * @description checks if Title exists
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document The returned document
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static titleExist(request, response, document) {
    if (document) {
      response.status(400).send({
        message: 'Title already exists',
      });
      return true;
    }
    return false;
  }

  /**
   * @description adds more properties to the query
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {integer} limit Number of rows to be returned
   * @param {integer} offset Number of rows to be skipped
   * @param {object} property query property
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static listQuery(request, response, limit, offset, property) {
    if (((limit && !(validator.isNumeric(limit)))
      && (offset && !(validator.isNumeric(offset))))
      || ((limit && limit <= 0) && (offset && offset < 0))) {
      response.status(400).send({
        message: 'Invalid Limit and Offset',
      });
      return false;
    } else if ((limit && !(validator.isNumeric(limit))) ||
    (limit && limit <= 0)) {
      response.status(400).send({
        message: 'Invalid Limit',
      });
      return false;
    } else if ((offset && !(validator.isNumeric(offset))) ||
    (offset && offset < 0)) {
      response.status(400).send({
        message: 'Invalid Offset',
      });
      return false;
    }
    property.limit = limit || 10;
    property.offset = offset || 0;
    response.property = property;
    return true;
  }

  /**
   * @description Checks if document exists
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document the returned document
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static isDocument(request, response, document) {
    if (!document) {
      response.status(404).send({
        message: 'Document Not Found'
      });
      return false;
    }
    return true;
  }

  /**
   * @description Checks Authorization for viewing
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document the returned document
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static isAllowed(request, response, document) {
    const allowed = [document.User.roleId, 1, 2];
    if ((document.access === 'Private' &&
      document.userId !== request.loggedInUser.id) ||
    (document.access === 'Role' && allowed.includes(request.loggedInUser.roleId)
    === false)) {
      response.status(403).send({
        message: 'You are not authorized to view this document'
      });
      return false;
    }
    return true;
  }

  /**
   * @description Checks Authorization for updating
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {integer} ownerId id of the document owner
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static allowUpdate(request, response, ownerId) {
    if (request.loggedInUser.roleId !== 1 &&
      request.loggedInUser.id !== parseInt(ownerId)) {
      response.status(403).send({
        message: 'You cannot update someone else\'s document',
      });
      return false;
    }
    return true;
  }

  /**
   * @description Checks Validity of parameters
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static isValidParams(request, response) {
    if (!request.body.title && validator.isEmpty(request.body.title)) {
      response.status(400).send({
        message: 'Title is Required',
      });
      return false;
    }
    if (!request.body.content && validator.isEmpty(request.body.content)) {
      response.status(400).send({
        message: 'Content is Required',
      });
      return false;
    }
    if (!request.body.access && validator.isEmpty(request.body.access)) {
      response.status(400).send({
        message: 'Access is Required',
      });
      return false;
    }
    if (!(['Public', 'Private', 'Role'].includes(request.body.access))) {
      response.status(400).send({
        message: 'Invalid Access Type',
      });
      return false;
    }
    return true;
  }

  /**
   * @description checks validation error
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} error server error
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static validationError(request, response, error) {
    if (error.toString() ===
      'SequelizeUniqueConstraintError: Validation error') {
      response.status(400).send({
        message: 'Your Edited Title already exists!!!',
      });
      return true;
    }
    return false;
  }

  /**
   * @description Checks Authorization for deleting
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {integer} ownerId id of the document owner
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static allowDelete(request, response, ownerId) {
    if (request.loggedInUser.roleId !== 1 &&
      request.loggedInUser.id !== parseInt(ownerId)) {
      response.status(403).send({
        message: 'You cannot delete someone else\'s document',
      });
      return false;
    }
    return true;
  }

  /**
   * @description Validates document ID for retrieval
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {any} documentId Id of the document to be retrieved
   * @returns {boolean} true or false
   * @memberof Utils
   */
  static documentIdValid(request, response, documentId) {
    if (!validator.isNumeric(documentId)) {
      response.status(400).send({
        message: 'Document Id must be an integer',
      });
      return false;
    }
    return true;
  }

  /**
   * @description Filters Search Result
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} document the returned document
   * @returns {boolean} false
   * @memberof Utils
   */
  static filter(request, response, document) {
    let count = 0;
    const newArr = [];
    while (count < document.length) {
      const allowed = [document[count].dataValues.User.dataValues.roleId, 1, 2];
      if (!((document[count].dataValues.access === 'Private'
      && document[count].dataValues.userId !== request.loggedInUser.id
      && request.loggedInUser.roleId === 3) ||
      (document[count].dataValues.access === 'Role'
      && allowed.includes(request.loggedInUser.roleId)
      === false))) {
        delete document[count].dataValues.User;
        newArr.push(document[count]);
      }
      count += 1;
    }
    if (newArr.length === 0) {
      response.status(404).send({ message: 'No Document Found' });
      return false;
    }
    response.document = newArr;
    return false;
  }

  /**
   * @description Paginates Result
   * @static
   * @param {object} request Client's request
   * @param {object} response Server Response
   * @param {object} object the returned document or user
   * @memberof Utils
   */
  static paginate(request, response, object) {
    const totalCount = object.length;
    let pageCount = Math.round(totalCount / (request.query.limit || 10));
    pageCount = (pageCount < 1 && totalCount > 0) ? 1 : pageCount;
    const page = Math.round((request.query.offset || 0) /
    (request.query.limit || 10)) + 1;
    response.status(200).send({ object,
      metaData: {
        page,
        pageCount,
        count: object.length,
        totalCount,
      }
    });
  }
}
<|endoftext|>