<commit_before>/* @flow */
// Vendor
import React from 'react';
import Measure from 'react-measure';
import Relay from 'react-relay/classic';
import QuestionIcon from 'react-icons/lib/fa/question-circle';
import * as d3 from 'd3';
import { compose, withState, withProps, lifecycle, mapProps } from 'recompose';
import JSURL from 'jsurl';
import { isEqual, sortBy } from 'lodash';

// Custom
import Column from '@ncigdc/uikit/Flex/Column';
import Row from '@ncigdc/uikit/Flex/Row';
import SpinnerParticle from '@ncigdc/uikit/Loaders/Particle';
import { Tooltip } from '@ncigdc/uikit/Tooltip';

import withRouter from '@ncigdc/utils/withRouter';
import { fetchApi } from '@ncigdc/utils/ajax';
import { setFilter, mergeQuery } from '@ncigdc/utils/filters';
import { removeEmptyKeys } from '@ncigdc/utils/uri';

import DoubleRingChart from '@ncigdc/components/Charts/DoubleRingChart';
import StackedBarChart from '@ncigdc/components/Charts/StackedBarChart';

import styled from '@ncigdc/theme/styled';
import { withTheme } from '@ncigdc/theme';
import significantConsequences from '@ncigdc/utils/filters/prepared/significantConsequences';
import type { TValueFilter } from '@ncigdc/utils/filters/types';

const color = d3.scaleOrdinal([
  ...d3.schemeCategory20,
  '#CE6DBD',
  '#AD494A',
  '#8C6D31',
  '#B5CF6B',
]);

type TProps = {
  setState: Function,
  projectIds: Array<string>,
  state: {
    numUniqueCases: number,
    topGenesWithCasesPerProject: { [gene_id: string]: { [project_id: string]: number, symbol: string}},
    projectsIsFetching: boolean,
    genesIsFetching: boolean,
    topGenesSource: Array<{
      gene_id: string,
      symbol: string,
    }>,
  },
  relay: {
    setVariables: Function,
  },
  hits: { edges: Array<Object> },
  theme: Object,
  query: Object,
  pathname: string,
  push: Function,
  explore: {
    genes: {
      hits: {
        edges: Array<{
          node: {
            gene_id: string,
            symbol: string,
          },
        }>,
      },
    },
  },
};

const Container = styled(Row, {
  marginBottom: '2rem',
  backgroundColor: 'white',
  border: '1px solid #ddd',
  borderRadius: '4px',
  height: '300px',
});

const initialState = {
  topGenesWithCasesPerProject: {},
  numUniqueCases: 0,
  projectsIsFetching: false,
  genesIsFetching: true,
  topGenesSource: [],
};

function getGenes({ relay, projectIds }: TProps): void {
  const commonFilters: Array<?TValueFilter> = [
    significantConsequences,
    projectIds.length ? {
      op: 'in',
      content: {
        field: 'cases.project.project_id',
        value: projectIds,
      },
    } : null,
  ].filter(Boolean);

  relay.setVariables({
    fetchGeneData: true,
    fmgCaseCount_filters: {
      op: 'AND',
      content: commonFilters,
    },
    fmgChart_filters: {
      op: 'AND',
      content: [
        ...commonFilters,
        {
          op: 'in',
          content: {
            field: 'genes.is_cancer_gene_census',
            value: [true],
          },
        },
      ],
    },
  });
}

const ProjectsChartsComponent = compose(
  withState('state', 'setState', initialState),
  withRouter,
  // eslint-disable-next-line fp/no-mutating-methods
  mapProps(props => ({ ...props, projectIds: props.hits.edges.map(x => x.node.project_id).sort() })),
  withProps({
    async fetchData(props: TProps): Promise<*> {
      const topGenesSource = (props.explore.genes.hits.edges || []).map(g => g.node);

      const { aggregations } = await fetchApi('analysis/top_cases_counts_by_genes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: {
          size: 0,
          gene_ids: topGenesSource.map(g => g.gene_id).join(),
        },
      });

      const caseAggs = aggregations.projects.buckets.filter(b => props.projectIds.includes(b.key));
      const numUniqueCases = props.explore.cases.hits.total;

      const topGenesWithCasesPerProject = caseAggs
        .reduce((acc, agg) => (
          agg.genes.my_genes.gene_id.buckets
            .reduce((genes, gene) => ({
              ...genes,
              [gene.key]: {
                ...genes[gene.key],
                [agg.key]: gene.doc_count,
              },
            }), acc)
        ), topGenesSource.reduce((acc, g) => ({ ...acc, [g.gene_id]: {} }), {}));

      props.setState(state => ({
        ...state,
        numUniqueCases,
        topGenesWithCasesPerProject,
        genesIsFetching: false,
        topGenesSource,
      }));
    },
  }),
  lifecycle({
    componentDidMount(): void {
      getGenes(this.props);
    },
    componentWillReceiveProps(nextProps: TProps): void {
      if (!isEqual(this.props.projectIds, nextProps.projectIds)) {
        this.props.setState(s => ({ ...s, genesIsFetching: true }));
        getGenes(nextProps);
      } else if (nextProps.explore && !isEqual(this.props.explore, nextProps.explore)) {
        this.props.setState(s => ({ ...s, genesIsFetching: true }));
        this.props.fetchData(nextProps);
      }
    },
  }),
  withTheme
)(({
  state: {
    numUniqueCases,
    topGenesWithCasesPerProject,
    projectsIsFetching,
    genesIsFetching,
    topGenesSource,
  },
  hits,
  theme,
  query,
  pathname,
  push,
}: TProps) => {
  const projects = hits.edges.map(x => x.node);

  const stackedBarData = topGenesSource.map(({ gene_id: geneId, symbol }) => ({
    symbol,
    gene_id: geneId,
    onClick: () => push(`/genes/${geneId}`),
    ...topGenesWithCasesPerProject[geneId],
    total: Object.keys(topGenesWithCasesPerProject[geneId])
      .filter(k => k !== 'symbol')
      .reduce((sum, projectId) => sum + topGenesWithCasesPerProject[geneId][projectId], 0),
  }));

  const doubleRingData = projects.reduce((acc, p) => {
    const primarySiteCasesCount = acc[p.primary_site]
      ? acc[p.primary_site].value + p.summary.case_count
      : p.summary.case_count;

    return {
      ...acc,
      [p.primary_site]: {
        value: primarySiteCasesCount,
        tooltip: (

            {p.primary_site}

            {primarySiteCasesCount.toLocaleString()} case{primarySiteCasesCount > 1 ? 's' : ''}

        ),
        clickHandler: () => {
          const newQuery = mergeQuery(
            { filters: setFilter({ field: 'projects.primary_site', value: [].concat(p.primary_site || []) }) },
            query,
            'toggle'
          );

          const q = removeEmptyKeys({ ...newQuery, filters: newQuery.filters && JSURL.stringify(newQuery.filters) });

          push({ pathname, query: q });
        },
        outer: [
          ...(acc[p.primary_site] || { outer: [] }).outer,
          {
            key: p.project_id,
            value: p.summary.case_count,
            tooltip: (

                {p.name}

                {p.summary.case_count.toLocaleString()} case{p.summary.case_count > 1 ? 's' : 0}

            ),
            clickHandler: () => {
              const newQuery = mergeQuery(
                { filters: setFilter({ field: 'projects.project_id', value: [].concat(p.project_id || []) }) },
                query,
                'toggle'
              );

              const q = removeEmptyKeys({
                ...newQuery, filters: newQuery.filters && JSURL.stringify(newQuery.filters),
              });

              push({ pathname, query: q });
            },
          },
        ],
      },
    };
  }, {});

  const totalCases = projects.reduce((sum, p) => sum + p.summary.case_count, 0);

   // there are 24 primary sites
  const projectsInTopGenes = Object.keys(topGenesWithCasesPerProject)
    .reduce((acc, g) => [...acc, ...Object.keys(topGenesWithCasesPerProject[g])], []);

  const primarySiteProjects = sortBy(projects, [
    p => projectsInTopGenes.includes(p),
    p => p.project_id,
  ])
    .reduce((acc, p, i) => ({
      ...acc,
      [p.primary_site]: {
        color: acc[p.primary_site] ? acc[p.primary_site].color : color(i),
        projects: [...(acc[p.primary_site] || { projects: [] }).projects, p.project_id],
      },
    }), {});

  // brighten project colors by a multiplier that's based on projects number, so the slices don't get too light
  // and if there's only two slices the colors are different enough
  const primarySiteToColor = Object.keys(primarySiteProjects).reduce((acc, primarySite) => ({
    ...acc,
    [primarySite]: {
      ...primarySiteProjects[primarySite],
      projects: primarySiteProjects[primarySite].projects.reduce((acc, project_id, i) => ({
        ...acc,
        [project_id]: d3.color(primarySiteProjects[primarySite].color)
          .brighter(0.5 * ((1 / primarySiteProjects[primarySite].projects.length) * i)),
      }), {}),
    },
  }), {});

  return (
    <Container>
      <Column style={{ paddingRight: '10px', minWidth: '550px', flexGrow: '2', flexBasis: '66%' }}>

          Top Mutated Cancer Genes in Selected Projects
          <Tooltip Component={From COSMIC Cancer Gene Census}>
            <QuestionIcon
              style={{ color: theme.greyScale7, marginLeft: '5px' }}
            />
          </Tooltip>

        {!genesIsFetching ? [

            {
              `${numUniqueCases.toLocaleString()} Unique Case${!numUniqueCases || numUniqueCases > 1 ? 's' : ''} with Somatic Mutation Data` // eslint-disable-line max-len
            }

,

            <Measure key='bar-chart'>
              {({ width }) =>
                <StackedBarChart
                  width={width}
                  height={170}
                  // relay score sorting isn't returned in reliable order
                  data={stackedBarData.sort((a, b) => b.total - a.total)}
                  projectsIdtoName={projects.reduce((acc, p) => ({ ...acc, [p.project_id]: p.name }), {})}
                  colors={Object.keys(primarySiteToColor)
                    .reduce((acc, pSite) => ({ ...acc, ...primarySiteToColor[pSite].projects }), {})
                  }
                  yAxis={{ title: 'Cases Affected' }}
                  styles={{
                    xAxis: { stroke: theme.greyScale4, textFill: theme.greyScale3 },
                    yAxis: { stroke: theme.greyScale4, textFill: theme.greyScale3 },
                  }}
                />
              }
            </Measure>
          ,
        ] :
        <Row style={{ justifyContent: 'center', paddingTop: '2em', paddingBottom: '2em' }}>
          <SpinnerParticle />
        </Row>
        }
      </Column>
      <Column style={{ minWidth: '200px', flexGrow: '1', flexBasis: '33%' }}>

          Case Distribution per Project

        {!projectsIsFetching ? [

            {`${totalCases.toLocaleString()} Case${totalCases === 0 || totalCases > 1 ? 's' : ''}
              across ${projects.length.toLocaleString()} Project${projects.length === 0 || projects.length > 1 ? 's' : ''}`
            }

,

            <DoubleRingChart
              key='pie-chart'
              colors={primarySiteToColor}
              data={
                Object.keys(doubleRingData)
                  .map(primary_site => ({
                    key: primary_site,
                    ...doubleRingData[primary_site],
                  })
                )}
              height={200}
              width={200}
            />
          ,
        ] :
        <Row style={{ justifyContent: 'center', paddingTop: '2em', paddingBottom: '2em' }}>
          <SpinnerParticle />
        </Row>
        }
      </Column>
    </Container>
  );
});

export const ProjectsChartsQuery = {
  initialVariables: {
    fetchGeneData: false,
    fmgCaseCount_filters: null,
    fmgChart_filters: null,
    score: 'case.project.project_id',
  },
  fragments: {
    explore: () => Relay.QL`
      fragment on Explore {
        cases { hits(first: 0 filters: $fmgCaseCount_filters) { total } }
        genes {
          h: hits {
            total
          }
        }

        genes @include(if: $fetchGeneData) {
          hits (first: 20 filters: $fmgChart_filters, score: $score) {
            total
            edges {
              node {
                score
                symbol
                gene_id
                filteredCases: case {
                  hits(first: 1 filters: $fmgChart_filters) {
                    total
                  }
                }
                allCases: case {
                  hits(first: 1) {
                    total
                  }
                }
              }
            }
          }
        }
      }
    `,
    hits: () => Relay.QL`
      fragment on ProjectConnection {
        total
        edges @relay(plural: true) {
          node {
            id
            project_id
            name
            disease_type
            program {
              name
            }
            primary_site
            summary {
              case_count
              data_categories {
                case_count
                data_category
              }
              file_count
            }
          }
        }
      }
    `,
  },
};

const ProjectsCharts = Relay.createContainer(
  ProjectsChartsComponent,
  ProjectsChartsQuery
);

export default ProjectsCharts;
<commit_msg>✨ PRTL-1138 projects numcases link explore (#1679)<commit_after>/* @flow */
// Vendor
import React from 'react';
import Measure from 'react-measure';
import Relay from 'react-relay/classic';
import QuestionIcon from 'react-icons/lib/fa/question-circle';
import * as d3 from 'd3';
import { compose, withState, withProps, lifecycle, mapProps, withPropsOnChange } from 'recompose';
import JSURL from 'jsurl';
import { isEqual, sortBy } from 'lodash';

// Custom
import Column from '@ncigdc/uikit/Flex/Column';
import Row from '@ncigdc/uikit/Flex/Row';
import SpinnerParticle from '@ncigdc/uikit/Loaders/Particle';
import { Tooltip } from '@ncigdc/uikit/Tooltip';

import withRouter from '@ncigdc/utils/withRouter';
import { fetchApi } from '@ncigdc/utils/ajax';
import { setFilter, mergeQuery } from '@ncigdc/utils/filters';
import { removeEmptyKeys } from '@ncigdc/utils/uri';

import DoubleRingChart from '@ncigdc/components/Charts/DoubleRingChart';
import StackedBarChart from '@ncigdc/components/Charts/StackedBarChart';
import ExploreLink from '@ncigdc/components/Links/ExploreLink';

import styled from '@ncigdc/theme/styled';
import { withTheme } from '@ncigdc/theme';
import significantConsequences from '@ncigdc/utils/filters/prepared/significantConsequences';
import type { TValueFilter } from '@ncigdc/utils/filters/types';

const color = d3.scaleOrdinal([
  ...d3.schemeCategory20,
  '#CE6DBD',
  '#AD494A',
  '#8C6D31',
  '#B5CF6B',
]);

type TProps = {
  setState: Function,
  projectIds: Array<string>,
  state: {
    numUniqueCases: number,
    topGenesWithCasesPerProject: { [gene_id: string]: { [project_id: string]: number, symbol: string}},
    projectsIsFetching: boolean,
    genesIsFetching: boolean,
    topGenesSource: Array<{
      gene_id: string,
      symbol: string,
    }>,
  },
  relay: {
    setVariables: Function,
  },
  hits: { edges: Array<Object> },
  theme: Object,
  query: Object,
  pathname: string,
  push: Function,
  explore: {
    genes: {
      hits: {
        edges: Array<{
          node: {
            gene_id: string,
            symbol: string,
          },
        }>,
      },
    },
  },
};

const Container = styled(Row, {
  marginBottom: '2rem',
  backgroundColor: 'white',
  border: '1px solid #ddd',
  borderRadius: '4px',
  height: '300px',
});

const initialState = {
  topGenesWithCasesPerProject: {},
  numUniqueCases: 0,
  projectsIsFetching: false,
  genesIsFetching: true,
  topGenesSource: [],
};

function getGenes({ relay, caseCountFilters }: TProps): void {
  relay.setVariables({
    fetchGeneData: true,
    fmgCaseCount_filters: caseCountFilters.length
      ? { op: 'AND', content: caseCountFilters }
      : null,
    fmgChart_filters: {
      op: 'AND',
      content: [
        significantConsequences,
        ...caseCountFilters,
        {
          op: 'in',
          content: {
            field: 'genes.is_cancer_gene_census',
            value: [true],
          },
        },
      ],
    },
  });
}

const ProjectsChartsComponent = compose(
  withState('state', 'setState', initialState),
  withRouter,
  // eslint-disable-next-line fp/no-mutating-methods
  mapProps(props => ({ ...props, projectIds: props.hits.edges.map(x => x.node.project_id).sort() })),
  withPropsOnChange(['projectIds'], ({ projectIds }) => ({
    caseCountFilters: [
      projectIds.length ? {
        op: 'in',
        content: {
          field: 'cases.project.project_id',
          value: projectIds,
        },
      } : null,
    ].filter(Boolean),
  })),
  withProps({
    async fetchData(props: TProps): Promise<*> {
      const topGenesSource = (props.explore.genes.hits.edges || []).map(g => g.node);

      const { aggregations } = await fetchApi('analysis/top_cases_counts_by_genes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: {
          size: 0,
          gene_ids: topGenesSource.map(g => g.gene_id).join(),
        },
      });

      const caseAggs = aggregations.projects.buckets.filter(b => props.projectIds.includes(b.key));
      const numUniqueCases = props.explore.cases.hits.total;

      const topGenesWithCasesPerProject = caseAggs
        .reduce((acc, agg) => (
          agg.genes.my_genes.gene_id.buckets
            .reduce((genes, gene) => ({
              ...genes,
              [gene.key]: {
                ...genes[gene.key],
                [agg.key]: gene.doc_count,
              },
            }), acc)
        ), topGenesSource.reduce((acc, g) => ({ ...acc, [g.gene_id]: {} }), {}));

      props.setState(state => ({
        ...state,
        numUniqueCases,
        topGenesWithCasesPerProject,
        genesIsFetching: false,
        topGenesSource,
      }));
    },
  }),
  lifecycle({
    componentDidMount(): void {
      getGenes(this.props);
    },
    componentWillReceiveProps(nextProps: TProps): void {
      if (!isEqual(this.props.projectIds, nextProps.projectIds)) {
        this.props.setState(s => ({ ...s, genesIsFetching: true }));
        getGenes(nextProps);
      } else if (nextProps.explore && !isEqual(this.props.explore, nextProps.explore)) {
        this.props.setState(s => ({ ...s, genesIsFetching: true }));
        this.props.fetchData(nextProps);
      }
    },
  }),
  withTheme
)(({
  state: {
    numUniqueCases,
    topGenesWithCasesPerProject,
    projectsIsFetching,
    genesIsFetching,
    topGenesSource,
  },
  hits,
  theme,
  query,
  pathname,
  push,
  caseCountFilters,
}: TProps) => {
  const projects = hits.edges.map(x => x.node);

  const stackedBarData = topGenesSource.map(({ gene_id: geneId, symbol }) => ({
    symbol,
    gene_id: geneId,
    onClick: () => push(`/genes/${geneId}`),
    ...topGenesWithCasesPerProject[geneId],
    total: Object.keys(topGenesWithCasesPerProject[geneId])
      .filter(k => k !== 'symbol')
      .reduce((sum, projectId) => sum + topGenesWithCasesPerProject[geneId][projectId], 0),
  }));

  const doubleRingData = projects.reduce((acc, p) => {
    const primarySiteCasesCount = acc[p.primary_site]
      ? acc[p.primary_site].value + p.summary.case_count
      : p.summary.case_count;

    return {
      ...acc,
      [p.primary_site]: {
        value: primarySiteCasesCount,
        tooltip: (

            {p.primary_site}

            {primarySiteCasesCount.toLocaleString()} case{primarySiteCasesCount > 1 ? 's' : ''}

        ),
        clickHandler: () => {
          const newQuery = mergeQuery(
            { filters: setFilter({ field: 'projects.primary_site', value: [].concat(p.primary_site || []) }) },
            query,
            'toggle'
          );

          const q = removeEmptyKeys({ ...newQuery, filters: newQuery.filters && JSURL.stringify(newQuery.filters) });

          push({ pathname, query: q });
        },
        outer: [
          ...(acc[p.primary_site] || { outer: [] }).outer,
          {
            key: p.project_id,
            value: p.summary.case_count,
            tooltip: (

                {p.name}

                {p.summary.case_count.toLocaleString()} case{p.summary.case_count > 1 ? 's' : 0}

            ),
            clickHandler: () => {
              const newQuery = mergeQuery(
                { filters: setFilter({ field: 'projects.project_id', value: [].concat(p.project_id || []) }) },
                query,
                'toggle'
              );

              const q = removeEmptyKeys({
                ...newQuery, filters: newQuery.filters && JSURL.stringify(newQuery.filters),
              });

              push({ pathname, query: q });
            },
          },
        ],
      },
    };
  }, {});

  const totalCases = projects.reduce((sum, p) => sum + p.summary.case_count, 0);

   // there are 24 primary sites
  const projectsInTopGenes = Object.keys(topGenesWithCasesPerProject)
    .reduce((acc, g) => [...acc, ...Object.keys(topGenesWithCasesPerProject[g])], []);

  const primarySiteProjects = sortBy(projects, [
    p => projectsInTopGenes.includes(p),
    p => p.project_id,
  ])
    .reduce((acc, p, i) => ({
      ...acc,
      [p.primary_site]: {
        color: acc[p.primary_site] ? acc[p.primary_site].color : color(i),
        projects: [...(acc[p.primary_site] || { projects: [] }).projects, p.project_id],
      },
    }), {});

  // brighten project colors by a multiplier that's based on projects number, so the slices don't get too light
  // and if there's only two slices the colors are different enough
  const primarySiteToColor = Object.keys(primarySiteProjects).reduce((acc, primarySite) => ({
    ...acc,
    [primarySite]: {
      ...primarySiteProjects[primarySite],
      projects: primarySiteProjects[primarySite].projects.reduce((acc, project_id, i) => ({
        ...acc,
        [project_id]: d3.color(primarySiteProjects[primarySite].color)
          .brighter(0.5 * ((1 / primarySiteProjects[primarySite].projects.length) * i)),
      }), {}),
    },
  }), {});

  return (
    <Container>
      <Column style={{ paddingRight: '10px', minWidth: '550px', flexGrow: '2', flexBasis: '66%' }}>

          Top Mutated Cancer Genes in Selected Projects
          <Tooltip Component={From COSMIC Cancer Gene Census}>
            <QuestionIcon
              style={{ color: theme.greyScale7, marginLeft: '5px' }}
            />
          </Tooltip>

        {!genesIsFetching ? [

            <ExploreLink
              query={{
                searchTableTab: 'cases',
                filters: caseCountFilters ? { op: 'and', content: caseCountFilters } : null,
              }}
            >{numUniqueCases.toLocaleString()}</ExploreLink>
            {
              ` Unique Case${!numUniqueCases || numUniqueCases > 1 ? 's' : ''} with Somatic Mutation Data` // eslint-disable-line max-len
            }

,

            <Measure key='bar-chart'>
              {({ width }) =>
                <StackedBarChart
                  width={width}
                  height={170}
                  // relay score sorting isn't returned in reliable order
                  data={stackedBarData.sort((a, b) => b.total - a.total)}
                  projectsIdtoName={projects.reduce((acc, p) => ({ ...acc, [p.project_id]: p.name }), {})}
                  colors={Object.keys(primarySiteToColor)
                    .reduce((acc, pSite) => ({ ...acc, ...primarySiteToColor[pSite].projects }), {})
                  }
                  yAxis={{ title: 'Cases Affected' }}
                  styles={{
                    xAxis: { stroke: theme.greyScale4, textFill: theme.greyScale3 },
                    yAxis: { stroke: theme.greyScale4, textFill: theme.greyScale3 },
                  }}
                />
              }
            </Measure>
          ,
        ] :
        <Row style={{ justifyContent: 'center', paddingTop: '2em', paddingBottom: '2em' }}>
          <SpinnerParticle />
        </Row>
        }
      </Column>
      <Column style={{ minWidth: '200px', flexGrow: '1', flexBasis: '33%' }}>

          Case Distribution per Project

        {!projectsIsFetching ? [

            {`${totalCases.toLocaleString()} Case${totalCases === 0 || totalCases > 1 ? 's' : ''}
              across ${projects.length.toLocaleString()} Project${projects.length === 0 || projects.length > 1 ? 's' : ''}`
            }

,

            <DoubleRingChart
              key='pie-chart'
              colors={primarySiteToColor}
              data={
                Object.keys(doubleRingData)
                  .map(primary_site => ({
                    key: primary_site,
                    ...doubleRingData[primary_site],
                  })
                )}
              height={200}
              width={200}
            />
          ,
        ] :
        <Row style={{ justifyContent: 'center', paddingTop: '2em', paddingBottom: '2em' }}>
          <SpinnerParticle />
        </Row>
        }
      </Column>
    </Container>
  );
});

export const ProjectsChartsQuery = {
  initialVariables: {
    fetchGeneData: false,
    fmgCaseCount_filters: null,
    fmgChart_filters: null,
    score: 'case.project.project_id',
  },
  fragments: {
    explore: () => Relay.QL`
      fragment on Explore {
        cases { hits(first: 0 filters: $fmgCaseCount_filters) { total } }
        genes {
          h: hits {
            total
          }
        }

        genes @include(if: $fetchGeneData) {
          hits (first: 20 filters: $fmgChart_filters, score: $score) {
            total
            edges {
              node {
                score
                symbol
                gene_id
                filteredCases: case {
                  hits(first: 1 filters: $fmgChart_filters) {
                    total
                  }
                }
                allCases: case {
                  hits(first: 1) {
                    total
                  }
                }
              }
            }
          }
        }
      }
    `,
    hits: () => Relay.QL`
      fragment on ProjectConnection {
        total
        edges @relay(plural: true) {
          node {
            id
            project_id
            name
            disease_type
            program {
              name
            }
            primary_site
            summary {
              case_count
              data_categories {
                case_count
                data_category
              }
              file_count
            }
          }
        }
      }
    `,
  },
};

const ProjectsCharts = Relay.createContainer(
  ProjectsChartsComponent,
  ProjectsChartsQuery
);

export default ProjectsCharts;
<|endoftext|>


<commit_before>import Icon from 'icon'
import React, { Component } from 'react'
import Select from 'react-select'
import _ from 'messages'
import filter from 'lodash/filter'
import forEach from 'lodash/forEach'
import groupBy from 'lodash/groupBy'
import keyBy from 'lodash/keyBy'
import map from 'lodash/map'
import sortBy from 'lodash/sortBy'
import { parse } from 'xo-remote-parser'

import {
  createGetObjectsOfType,
  createGetTags,
  createSelector
} from 'selectors'

import {
  autobind,
  connectStore,
  formatSize,
  propTypes
} from 'utils'

import {
  subscribeRemotes
} from 'xo'

// ===================================================================

@propTypes({
  autoFocus: propTypes.bool,
  defaultValue: propTypes.any,
  disabled: propTypes.bool,
  multi: propTypes.bool,
  onChange: propTypes.func,
  placeholder: propTypes.string,
  predicate: propTypes.func,
  required: propTypes.bool
})
class GenericSelect extends Component {
  constructor (props) {
    super(props)
    this.state = {
      value: this._setValue(props.defaultValue || (props.multi ? [] : ''))
    }
  }

  // Supports id strings and objects.
  _setValue (value) {
    if (this.props.multi) {
      return map(value, object => object.id || object)
    }

    return value.id || value || ''
  }

  componentWillReceiveProps (props) {
    this.setState({
      options: this._computeOptions(props)
    })
  }

  componentWillMount () {
    this.setState({
      options: this._computeOptions(this.props)
    })
  }

  get value () {
    const { objects } = this.props
    const { value } = this.state

    if (this.props.multi) {
      return map(value, value => objects[value.value || value])
    }

    return objects[value.value || value]
  }

  set value (value) {
    this.setState({
      value: this._setValue(value)
    })
  }

  @autobind
  _handleChange (value) {
    const { onChange } = this.props

    this.setState({
      value
    }, onChange && (() => { onChange(this.value) }))
  }

  _renderOption (option) {
    const { type } = option

    return (

        {type && <Icon icon={type} />} {option.label}

    )
  }
  // Unable to use @autobind here.
  // The inherited class cannot use autobind else.
  _renderOption = ::this._renderOption

  render () {
    const { props } = this

    return (
      <Select
        autofocus={props.autoFocus}
        disabled={props.disabled}
        multi={props.multi}
        onChange={this._handleChange}
        openOnFocus
        optionRenderer={this._renderOption}
        options={this.state.options}
        placeholder={props.placeholder || this._placeholder}
        required={props.required}
        value={this.state.value}
        valueRenderer={this._renderValue}
      />
    )
  }
}

// ===================================================================

@connectStore(() => {
  const getHosts = createGetObjectsOfType('host').filter(
    (state, props) => props.predicate
  )
  const getPools = createGetObjectsOfType('pool').pick(
    createSelector(
      getHosts,
      hosts => map(hosts, '$pool')
    )
  ).sort()
  const getHostsByPool = getHosts.groupBy('$pool')

  return (state, props) => ({
    hostsByPool: getHostsByPool(state, props),
    objects: getHosts(state, props),
    pools: getPools(state, props)
  })
}, { withRef: true })
export class SelectHost extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectHosts')
  }

  _computeOptions (props) {
    let newOptions = []

    forEach(props.pools, pool => {
      const poolId = pool.id

      newOptions.push({
        label: pool.name_label || poolId,
        disabled: true,
        type: 'pool'
      })

      newOptions.push.apply(newOptions,
        map(props.hostsByPool[poolId], host => {
          const { id } = host
          return {
            value: id,
            label: host.name_label || id,
            type: 'host'
          }
        })
      )
    })

    return newOptions
  }
}

// ===================================================================

@connectStore(() => {
  const getPools = createGetObjectsOfType('pool').filter(
    (state, props) => props.predicate
  )

  return (state, props) => ({
    objects: getPools(state, props)
  })
}, { withRef: true })
export class SelectPool extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectPools')
  }

  _computeOptions (props) {
    return map(
      props.objects,
      pool => {
        const { id } = pool
        return {
          value: id,
          label: pool.name_label || id,
          type: 'pool'
        }
      }
    )
  }
}

// ===================================================================

export class SelectRemote extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectRemotes')
  }

  componentWillMount () {
    this.componentWillUnmount = subscribeRemotes(remotes => {
      this.setState({
        remotes: keyBy(remotes, 'id'),
        options: this._computeOptions(this.props, remotes)
      })
    })
  }

  get value () {
    const { remotes, value } = this.state

    if (this.props.multi) {
      return map(value, value => remotes[value.value || value])
    }

    return remotes[value.value || value]
  }

  set value (value) {
    super.value = value
  }

  _computeOptions (props, remotes = this.state.remotes) {
    const remotesByGroup = groupBy(
      map(
        props.predicate ? filter(remotes, props.predicate) : remotes,
        parse
      ), 'type'
    )
    let newOptions = []

    forEach(remotesByGroup, (remotes, label) => {
      newOptions.push({
        label,
        disabled: true
      })

      newOptions.push.apply(newOptions,
        map(sortBy(remotes, 'name'), remote => ({
          value: remote.id,
          label: remote.name
        }))
      )
    })

    return newOptions
  }
}

// ===================================================================

@connectStore(() => {
  const getSrs = createGetObjectsOfType('SR').filter([
    sr => sr.content_type === 'user'
  ])
  const getHosts = createGetObjectsOfType('host').pick(
    createSelector(
      getSrs,
      srs => map(srs, '$container')
    )
  ).sort()
  const getPools = createGetObjectsOfType('pool').pick(
    createSelector(
      getSrs,
      srs => map(srs, '$container')
    )
  ).sort()
  const getContainers = createSelector(
    getHosts,
    getPools,
    (hosts, pools) => hosts.concat(pools)
  )
  const getSrsByContainer = getSrs.groupBy('$container')

  return (state, props) => ({
    containers: getContainers(state, props),
    objects: getSrs(state, props),
    srsByContainer: getSrsByContainer(state, props)
  })
}, { withRef: true })
export class SelectSr extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectSrs')
  }

  _computeOptions (props) {
    let newOptions = []

    forEach(props.containers, container => {
      const containerId = container.id
      const containerLabel = container.name_label || containerId

      newOptions.push({
        label: containerLabel,
        disabled: true,
        type: container.type
      })

      newOptions.push.apply(newOptions,
        map(props.srsByContainer[containerId], sr => {
          const { id } = sr
          return {
            value: id,
            label: `${sr.name_label || id} (${formatSize(sr.size)})`,
            type: 'disk'
          }
        })
      )
    })

    return newOptions
  }
}

// ===================================================================

@connectStore(() => {
  const getVms = createGetObjectsOfType('VM').filter(
    (state, props) => props.predicate
  )
  const getHosts = createGetObjectsOfType('host').pick(
    createSelector(
      getVms,
      vms => map(vms, '$container')
    )
  ).sort()
  const getPools = createGetObjectsOfType('pool').pick(
    createSelector(
      getVms,
      vms => map(vms, '$container')
    )
  ).sort()
  const getContainers = createSelector(
    getHosts,
    getPools,
    (hosts, pools) => hosts.concat(pools)
  )
  const getVmsByContainer = getVms.groupBy('$container')

  return (state, props) => ({
    containers: getContainers(state, props),
    objects: getVms(state, props),
    vmsByContainer: getVmsByContainer(state, props)
  })
}, { withRef: true })
export class SelectVm extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectVms')
  }

  _computeOptions (props) {
    let newOptions = []

    forEach(sortBy(props.containers, [ 'type', 'name_label' ]), container => {
      const containerId = container.id
      const containerLabel = container.name_label || containerId

      newOptions.push({
        label: containerLabel,
        disabled: true,
        type: container.type
      })

      newOptions.push.apply(newOptions,
        map(props.vmsByContainer[containerId], vm => {
          const { id } = vm
          return {
            value: id,
            label: `${vm.name_label || id} (${containerLabel})`,
            vm
          }
        })
      )
    })

    return newOptions
  }

  @autobind
  _renderOption (option) {
    const { vm } = option

    if (!vm) {
      return super._renderOption(option)
    }

    return (

        <Icon icon={`vm-${vm.power_state.toLowerCase()}`} /> {option.label}

    )
  }

  @autobind
  _renderValue (option) {
    return (

        <Icon icon={`vm-${option.vm.power_state.toLowerCase()}`} /> {option.label}

    )
  }
}

// ===================================================================

@connectStore({
  tags: createGetTags().filter(
    (state, props) => props.predicate
  ).sort()
}, { withRef: true })
export class SelectTag extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectTags')
  }

  get value () {
    const { value } = this.state

    if (this.props.multi) {
      return map(value, value => value.value || value)
    }

    return value.value || value
  }

  set value (value) {
    super.value = value
  }

  _computeOptions (props) {
    return map(
      props.tags,
      tag => ({
        value: tag,
        label: tag,
        type: 'tags'
      }))
  }
}
<commit_msg>perf(select-objects/SelectVm): improve connectStore<commit_after>import _ from 'messages'
import filter from 'lodash/filter'
import forEach from 'lodash/forEach'
import groupBy from 'lodash/groupBy'
import Icon from 'icon'
import keyBy from 'lodash/keyBy'
import keys from 'lodash/keys'
import map from 'lodash/map'
import React, { Component } from 'react'
import Select from 'react-select'
import sortBy from 'lodash/sortBy'
import { parse } from 'xo-remote-parser'

import {
  createGetObjectsOfType,
  createGetTags,
  createSelector
} from 'selectors'

import {
  autobind,
  connectStore,
  formatSize,
  propTypes
} from 'utils'

import {
  subscribeRemotes
} from 'xo'

// ===================================================================

@propTypes({
  autoFocus: propTypes.bool,
  defaultValue: propTypes.any,
  disabled: propTypes.bool,
  multi: propTypes.bool,
  onChange: propTypes.func,
  placeholder: propTypes.string,
  predicate: propTypes.func,
  required: propTypes.bool
})
class GenericSelect extends Component {
  constructor (props) {
    super(props)
    this.state = {
      value: this._setValue(props.defaultValue || (props.multi ? [] : ''))
    }
  }

  // Supports id strings and objects.
  _setValue (value) {
    if (this.props.multi) {
      return map(value, object => object.id || object)
    }

    return value.id || value || ''
  }

  componentWillReceiveProps (props) {
    this.setState({
      options: this._computeOptions(props)
    })
  }

  componentWillMount () {
    this.setState({
      options: this._computeOptions(this.props)
    })
  }

  get value () {
    const { objects } = this.props
    const { value } = this.state

    if (this.props.multi) {
      return map(value, value => objects[value.value || value])
    }

    return objects[value.value || value]
  }

  set value (value) {
    this.setState({
      value: this._setValue(value)
    })
  }

  @autobind
  _handleChange (value) {
    const { onChange } = this.props

    this.setState({
      value
    }, onChange && (() => { onChange(this.value) }))
  }

  _renderOption (option) {
    const { type } = option

    return (

        {type && <Icon icon={type} />} {option.label}

    )
  }
  // Unable to use @autobind here.
  // The inherited class cannot use autobind else.
  _renderOption = ::this._renderOption

  render () {
    const { props } = this

    return (
      <Select
        autofocus={props.autoFocus}
        disabled={props.disabled}
        multi={props.multi}
        onChange={this._handleChange}
        openOnFocus
        optionRenderer={this._renderOption}
        options={this.state.options}
        placeholder={props.placeholder || this._placeholder}
        required={props.required}
        value={this.state.value}
        valueRenderer={this._renderValue}
      />
    )
  }
}

// ===================================================================

@connectStore(() => {
  const getHosts = createGetObjectsOfType('host').filter(
    (state, props) => props.predicate
  )
  const getPools = createGetObjectsOfType('pool').pick(
    createSelector(
      getHosts,
      hosts => map(hosts, '$pool')
    )
  ).sort()
  const getHostsByPool = getHosts.groupBy('$pool')

  return (state, props) => ({
    hostsByPool: getHostsByPool(state, props),
    objects: getHosts(state, props),
    pools: getPools(state, props)
  })
}, { withRef: true })
export class SelectHost extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectHosts')
  }

  _computeOptions (props) {
    let newOptions = []

    forEach(props.pools, pool => {
      const poolId = pool.id

      newOptions.push({
        label: pool.name_label || poolId,
        disabled: true,
        type: 'pool'
      })

      newOptions.push.apply(newOptions,
        map(props.hostsByPool[poolId], host => {
          const { id } = host
          return {
            value: id,
            label: host.name_label || id,
            type: 'host'
          }
        })
      )
    })

    return newOptions
  }
}

// ===================================================================

@connectStore(() => {
  const getPools = createGetObjectsOfType('pool').filter(
    (state, props) => props.predicate
  )

  return (state, props) => ({
    objects: getPools(state, props)
  })
}, { withRef: true })
export class SelectPool extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectPools')
  }

  _computeOptions (props) {
    return map(
      props.objects,
      pool => {
        const { id } = pool
        return {
          value: id,
          label: pool.name_label || id,
          type: 'pool'
        }
      }
    )
  }
}

// ===================================================================

export class SelectRemote extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectRemotes')
  }

  componentWillMount () {
    this.componentWillUnmount = subscribeRemotes(remotes => {
      this.setState({
        remotes: keyBy(remotes, 'id'),
        options: this._computeOptions(this.props, remotes)
      })
    })
  }

  get value () {
    const { remotes, value } = this.state

    if (this.props.multi) {
      return map(value, value => remotes[value.value || value])
    }

    return remotes[value.value || value]
  }

  set value (value) {
    super.value = value
  }

  _computeOptions (props, remotes = this.state.remotes) {
    const remotesByGroup = groupBy(
      map(
        props.predicate ? filter(remotes, props.predicate) : remotes,
        parse
      ), 'type'
    )
    let newOptions = []

    forEach(remotesByGroup, (remotes, label) => {
      newOptions.push({
        label,
        disabled: true
      })

      newOptions.push.apply(newOptions,
        map(sortBy(remotes, 'name'), remote => ({
          value: remote.id,
          label: remote.name
        }))
      )
    })

    return newOptions
  }
}

// ===================================================================

@connectStore(() => {
  const getSrs = createGetObjectsOfType('SR').filter([
    sr => sr.content_type === 'user'
  ])
  const getHosts = createGetObjectsOfType('host').pick(
    createSelector(
      getSrs,
      srs => map(srs, '$container')
    )
  ).sort()
  const getPools = createGetObjectsOfType('pool').pick(
    createSelector(
      getSrs,
      srs => map(srs, '$container')
    )
  ).sort()
  const getContainers = createSelector(
    getHosts,
    getPools,
    (hosts, pools) => hosts.concat(pools)
  )
  const getSrsByContainer = getSrs.groupBy('$container')

  return (state, props) => ({
    containers: getContainers(state, props),
    objects: getSrs(state, props),
    srsByContainer: getSrsByContainer(state, props)
  })
}, { withRef: true })
export class SelectSr extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectSrs')
  }

  _computeOptions (props) {
    let newOptions = []

    forEach(props.containers, container => {
      const containerId = container.id
      const containerLabel = container.name_label || containerId

      newOptions.push({
        label: containerLabel,
        disabled: true,
        type: container.type
      })

      newOptions.push.apply(newOptions,
        map(props.srsByContainer[containerId], sr => {
          const { id } = sr
          return {
            value: id,
            label: `${sr.name_label || id} (${formatSize(sr.size)})`,
            type: 'disk'
          }
        })
      )
    })

    return newOptions
  }
}

// ===================================================================

@connectStore(() => {
  const getVms = createGetObjectsOfType('VM').filter(
    (state, props) => props.predicate
  )

  const getContainerIds = createSelector(
    getVms,
    vms => {
      // It makes sense to create a set instead of simply mapping
      // because there are much fewer containers than VMs.
      const set = {}
      forEach(vms, vm => {
        set[vm.$container] = true
      })
      return keys(set)
    }
  )
  const getHostContainers = createGetObjectsOfType('host').pick(getContainerIds).sort()
  const getPoolContainers = createGetObjectsOfType('pool').pick(getContainerIds).sort()

  const getVmsByContainer = getVms.sort().groupBy('$container')

  return (state, props) => ({
    hostContainers: getHostContainers(state, props),
    poolContainers: getPoolContainers(state, props),
    objects: getVms(state, props),
    vmsByContainer: getVmsByContainer(state, props)
  })
}, { withRef: true })
export class SelectVm extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectVms')
  }

  _computeOptions (props) {
    let newOptions = []

    const makeOptionsForContainer = container => {
      const containerId = container.id
      const containerLabel = container.name_label || containerId

      newOptions.push({
        label: containerLabel,
        disabled: true,
        type: container.type
      })

      newOptions.push.apply(newOptions,
        map(props.vmsByContainer[containerId], vm => {
          const { id } = vm
          return {
            value: id,
            label: `${vm.name_label || id} (${containerLabel})`,
            vm
          }
        })
      )
    }
    forEach(props.hostContainers, makeOptionsForContainer)
    forEach(props.poolContainers, makeOptionsForContainer)

    return newOptions
  }

  @autobind
  _renderOption (option) {
    const { vm } = option

    if (!vm) {
      return super._renderOption(option)
    }

    return (

        <Icon icon={`vm-${vm.power_state.toLowerCase()}`} /> {option.label}

    )
  }

  @autobind
  _renderValue (option) {
    return (

        <Icon icon={`vm-${option.vm.power_state.toLowerCase()}`} /> {option.label}

    )
  }
}

// ===================================================================

@connectStore({
  tags: createGetTags().filter(
    (state, props) => props.predicate
  ).sort()
}, { withRef: true })
export class SelectTag extends GenericSelect {
  constructor (props) {
    super(props)
    this._placeholder = _('selectTags')
  }

  get value () {
    const { value } = this.state

    if (this.props.multi) {
      return map(value, value => value.value || value)
    }

    return value.value || value
  }

  set value (value) {
    super.value = value
  }

  _computeOptions (props) {
    return map(
      props.tags,
      tag => ({
        value: tag,
        label: tag,
        type: 'tags'
      }))
  }
}
<|endoftext|>


<commit_before>'use strict'

var Proback = require('proback.js')

var Communication = require('../lib/Communication')
var Barrel = require('../lib/Barrel')
var Blower = require('../lib/Blower')
var Firestormstarter = require('../lib/Firestormstarter')
var Flamestarter = require('../lib/Flamestarter')

require('./ES7Fixer')

var VERSION = exports.VERSION = '4.6.6'

var _ = require('isa.js')
var async = require('async')

var Assigner = require('assign.js')
var assigner = new Assigner()

function purify ( obj, config, level, path ) {
	if (!obj) return obj
	if ( _.isDate(obj) || _.isBoolean(obj) || _.isNumber(obj) || _.isString(obj) || _.isRegExp(obj) )
		return obj
	if ( _.isFunction(obj) )
		return null
	if ( _.isArray(obj) ) {
		var arr = []
		obj.forEach( function ( element ) {
			if ( path.includes( element ) ) return
			path.push( element )
			arr.push( arr.length > config.arrayMaxSize ? '...' : purifyObj( element, config, level + 1, path ) )
		} )
		return arr
	}
	if ( _.isObject(obj) ) {
		var res = {}
		for (var key in obj)
			if ( key && obj[key] ) {
				if ( path.includes( obj[key] ) ) continue
				path.push( obj[key] )
				var resObj = level > config.maxLevel ? '...' : purify( obj[key], config, level + 1, path )
				if (resObj)
					res[key] = resObj
			}
		return res
	}
	return '...'
}

function purifyObj ( obj, config, level, path ) {
	if (!obj) return {}
	if ( _.isDate(obj) || _.isBoolean(obj) || _.isNumber(obj) || _.isString(obj) || _.isRegExp(obj) || _.isFunction(obj) || _.isArray(obj) )
		return { message: obj }
	if ( _.isObject(obj) )
		return purify( obj, config, level, path )
	return {}
}

/**
* Main control point of the messaging system
*
* @class Inflicter
* @constructor
*/
function Inflicter ( options, callback ) {
	this.options = assigner.assign( {}, require('./DefaultConfig'), options || {} )
	this.options.name = this.options.name || 'Inflicter'
	this.options.context = this.options.context || this.options.name
	this.options.division = this.options.division || this.options.name

	this.options.millieu = this.options.millieu || {}

	this.name = this.options.name
	this.context = this.options.context
	this.division = this.options.division
	this.connectedDivisions = this.options.connectedDivisions || []

	var self = this

	Communication.setupSecurity( self.options.idLength || 16 )

	this.Blower = this.options.Blower || Blower

	self.purifyConfig = { arrayMaxSize: this.options.arrayMaxSize || 100, maxLevel: this.options.maxLevel || 3 }
	self.logger = this.options.logger
	self.logger.harconlog = function ( err, message, obj, level ) {
		this.log( err ? 'error' : (level || 'debug'), err ? (err.message || err.toString()) : message, assigner.assign( purifyObj(obj, self.purifyConfig, 0, []), { 'harcon': VERSION } ) )
	}.bind( self.logger )

	this.Barrel = this.options.Barrel || Barrel
	this.Firestormstarter = this.options.Firestormstarter || Firestormstarter
	this.Flamestarter = this.options.Flamestarter || Flamestarter

	this.barrel = new this.Barrel( )
	var barrelConfig = assigner.assign(
		this.options.barrel || {},
		{ name: this.name, division: this.division, logger: this.logger, connectedDivisions: this.connectedDivisions, igniteLevel: this.options.igniteLevel, seals: this.options.seals }
	)
	this.barrel.init( barrelConfig, function (err) {
		if ( err ) return callback(err)

		self.connectedDivisions.forEach(function ( division ) {
			self.barrel.newDivision( division, function (err) {
				if (err) self.logger.harconlog(err)
			} )
		})

		self.inflicterContext = (self.options.environment || {})
		self.inflicterContext.logger = self.logger

		self.sliceArguments = function ( ) {
			var args = new Array(arguments.length)
			for (var i = 0; i < args.length; i += 1) {
				args[i] = arguments[i]
			}
			return args
		}

		self.addicts( {
			name: 'Inflicter',
			options: self.options,
			Barrel: self.Barrel,
			Blower: self.Blower,
			Firestormstarter: self.Firestormstarter,
			Flamestarter: self.Flamestarter,
			barrel: self.barrel,
			logger: self.logger,
			division: self.division,
			context: self.context,
			detracts: self.detracts,
			addicts: self.addicts,
			addict: self.addict,
			sliceArguments: self.sliceArguments,
			sendNotification: self.sendNotification,
			shifted: self.shifted,
			divisions: self.divisions,
			vivid: function ( cb ) {
				cb( null, 'Hello.' )
			}
		}, {}, function ( err, res ) {
			self.barrel.systemFirestarter = self.systemFirestarter = self.barrel.firestarter( res.name )
			self.logger.harconlog( null, 'Harcon started.', { }, 'info' )

			if ( callback )
				callback(err, res)
		} )
	} )
}

var inflicter = Inflicter.prototype

inflicter.sendNotification = function ( payload, events ) {
	var self = this
	events.forEach( function ( event ) {
		self.ignite( event, payload, function (err) {
			if (err) self.harconlog(err)
		} )
	} )
}

inflicter.shifted = function ( component, callback ) {
	var self = this

	if ( _.isString( component.state ) )
		self.sendNotification( component.state, component.notifyTos[ component.state ] || [] )
	else Object.keys(component.state).forEach( function (key) {
		self.sendNotification( component.state[key], component.notifyTos[ key ] || [] )
	} )

	callback()
}

inflicter.setWarper = function ( division, warper ) {
	return this.barrel.setWarper( division, warper )
}

inflicter.warpers = function () {
	return this.barrel.warpers( )
}

inflicter.divisions = function ( callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		var divisions = self.barrel.divisions( )
		Proback.returner( null, divisions, callback, resolve, reject )
	} )
}

inflicter.entities = function ( division, callback ) {
	var self = this
	if ( _.isFunction(division) ) {
		callback = division
		division = null
	}
	return new Promise( function (resolve, reject) {
		var entities = self.barrel.entities( division )
		Proback.returner( null, entities, callback, resolve, reject )
	} )
}

inflicter.listener = function ( name, callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		var listener = self.barrel.listener( name )
		Proback.returner( null, listener, callback, resolve, reject )
	} )
}

inflicter.pendingComms = function (callback) {
	var self = this
	var comms = self.barrel.firestarters.map( function (fs) {
		return fs.blower
	} ). filter( function (blower) { return !!blower } ).map( function (blower) {
		return blower.comms
	})
	callback( null, comms )
}

/**
* Activates a component
*
* @method activity
* @param {String} name The name of the component to be activated
*/
inflicter.activate = function ( name ) {
	return this.barrel.activity( name, true )
}

/**
* Deactivates a component
*
* @method activity
* @param {String} name The name of the component to be activated
*/
inflicter.deactivate = function ( name ) {
	return this.barrel.activity( name, false )
}

/**
* Unregisters an object-type lister
*
* @method detracts
* @param {Object} object Object
*/
inflicter.detracts = function ( object, callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		self.barrel.castOf( object.name, Proback.handler( callback, resolve, reject ) )
	} )
}

/**
* Registers a new object-type lister
*
* @method addicts
* @param {Object} object Object
*/
inflicter.addicts = function ( object, options, callback ) {
	if ( _.isFunction( options ) ) {
		callback = options
		options = null
	}

	var self = this

	return new Promise( function (resolve, reject) {
		self.detracts( object )
		.then( function () {
			if ( object.division ) {
				if ( object.division !== self.division && !object.division.startsWith( self.division + '.' ) )
					object.division = self.division + '.' + object.division
			}
			else object.division = self.division

			object.inflicterContext = self.inflicterContext
			var blower = new self.Blower( )
			var fss = new self.Firestormstarter( self.options, self.barrel, object, blower, self.logger )
			fss.sliceArguments = self.sliceArguments

			var fns = []
			fns.push( function (cb) {
				blower.init( assigner.assign(self.options.blower || {}, { barrel: self.barrel }), function (err) {
					if ( err ) self.logger.error( err, 'Unable to init Blower' )
					cb( err )
				} )
			} )

			if ( object.init && _.isFunction( object.init ) ) {
				fns.push( function (cb) {
					var componentConfig = assigner.assign( {}, self.options.millieu, self.options[fss.name], options )
					componentConfig.inflicter = self
					try {
						if ( object.init.length === 1 ) {
							object.init( componentConfig )
							cb()
						} else
							object.init( componentConfig, cb )
					} catch ( err ) {
						self.logger.error( err, 'Unable to initialize', fss.name )
						cb( err )
					}
				} )
			}

			fns.push( function (cb) {
				return self.barrel.affiliate( fss, function (err) {
					if ( err )
						self.logger.error( err, 'Unable to affiliate entity', fss.name )
					cb( err, {
						harcon: self, name: fss.name, context: fss.context, division: fss.division, services: fss.services(), rest: !!object.rest, websocket: !!object.websocket
					} )
				} )
			} )

			async.series( fns, function (err, res) {
				if ( err ) return Proback.rejecter( err, callback, reject )
				Proback.resolver( res[ res.length - 1 ], callback, resolve )
			} )
		} )
		.catch( function (reason) {
			Proback.rejecter( reason, callback, reject )
		} )
	})
}

/**
* Registers a new function-type lister
*
* @method addict
* @param {String} division, mandatory
* @param {String} name Name of the listener - needed for logging
* @param {String} eventName Eventname subscription
* @param {Function} fn Listener function
*/
inflicter.addict = function ( division, name, eventName, fn, callback ) {
	var self = this

	return new Promise( function (resolve, reject) {
		var fns = []
		var blower = new self.Blower( )
		fns.push( function (cb) {
			blower.init( self.options.blower || {}, function (err) {
				if ( err ) self.logger.error( err, 'Unable to init Blower' )
				cb( err )
			} )
		} )
		fns.push( function (cb) {
			var flamestarter = new self.Flamestarter( self.options, self.barrel, division || self.division, name, eventName, fn, blower, self.logger )
			flamestarter.sliceArguments = self.sliceArguments
			self.barrel.affiliate( flamestarter, cb )
		} )
		async.series( fns, function (err, res) {
			if ( err ) return Proback.rejecter( err, callback, reject )
			Proback.resolver( res[ res.length - 1 ], callback, resolve )
		} )
	} )
}

/**
* Creates a new event-flow by a starting-event without external ID or division
* The parameter list is a vararg, see parameters below
*
* @method simpleIgnite
* @param {String} event Name of the even to emit, mandatory
* @param {String} params A vararg element possessing the objects to be sent with the message. Can be empty
* @param {Function} callback Mandatory callback function as last element.
*/
inflicter.simpleIgnite = function ( ) {
	var args = [ null, null, this.division ].concat( this.sliceArguments.apply( this, arguments ) )
	return this.systemFirestarter.ignite.apply( this.systemFirestarter, args )
}

/**
* Creates a new event-flow by a starting-event.
* The parameter list is a vararg, see parameters below
*
* @method erupt
* @param {String} external ID, mandatory, can be null
* @param {String} division, mandatory
* @param {String} event Name of the even to emit, mandatory
* @param {String} params A vararg element possessing the objects to be sent with the message. Can be empty
*/
inflicter.erupt = function ( ) {
	return this.systemFirestarter.erupt.apply( this.systemFirestarter, arguments )
}

/**
* Creates a new event-flow by a starting-event.
* The parameter list is a vararg, see parameters below
*
* @method ignite
* @param {String} external ID, mandatory, can be null
* @param {String} division, mandatory
* @param {String} event Name of the even to emit, mandatory
* @param {String} params A vararg element possessing the objects to be sent with the message. Can be empty
* @param {Function} callback Mandatory callback function as last element.
*/
inflicter.ignite = function ( ) {
	return this.systemFirestarter.ignite.apply( this.systemFirestarter, arguments )
}

/**
* Notifies the harcon event-system to close all open connection
*
* @method close
*/
inflicter.close = function ( callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		self.barrel.close( Proback.handler( callback, resolve, reject ) )
	} )
}

Inflicter.Communication = Communication
Inflicter.Barrel = Barrel
Inflicter.Blower = Blower
Inflicter.Firestormstarter = Firestormstarter
Inflicter.Flamestarter = Flamestarter

module.exports = Inflicter
<commit_msg>fixed versioning<commit_after>'use strict'

var Proback = require('proback.js')

var Communication = require('../lib/Communication')
var Barrel = require('../lib/Barrel')
var Blower = require('../lib/Blower')
var Firestormstarter = require('../lib/Firestormstarter')
var Flamestarter = require('../lib/Flamestarter')

require('./ES7Fixer')

var VERSION = exports.VERSION = '4.7.0'

var _ = require('isa.js')
var async = require('async')

var Assigner = require('assign.js')
var assigner = new Assigner()

function purify ( obj, config, level, path ) {
	if (!obj) return obj
	if ( _.isDate(obj) || _.isBoolean(obj) || _.isNumber(obj) || _.isString(obj) || _.isRegExp(obj) )
		return obj
	if ( _.isFunction(obj) )
		return null
	if ( _.isArray(obj) ) {
		var arr = []
		obj.forEach( function ( element ) {
			if ( path.includes( element ) ) return
			path.push( element )
			arr.push( arr.length > config.arrayMaxSize ? '...' : purifyObj( element, config, level + 1, path ) )
		} )
		return arr
	}
	if ( _.isObject(obj) ) {
		var res = {}
		for (var key in obj)
			if ( key && obj[key] ) {
				if ( path.includes( obj[key] ) ) continue
				path.push( obj[key] )
				var resObj = level > config.maxLevel ? '...' : purify( obj[key], config, level + 1, path )
				if (resObj)
					res[key] = resObj
			}
		return res
	}
	return '...'
}

function purifyObj ( obj, config, level, path ) {
	if (!obj) return {}
	if ( _.isDate(obj) || _.isBoolean(obj) || _.isNumber(obj) || _.isString(obj) || _.isRegExp(obj) || _.isFunction(obj) || _.isArray(obj) )
		return { message: obj }
	if ( _.isObject(obj) )
		return purify( obj, config, level, path )
	return {}
}

/**
* Main control point of the messaging system
*
* @class Inflicter
* @constructor
*/
function Inflicter ( options, callback ) {
	this.options = assigner.assign( {}, require('./DefaultConfig'), options || {} )
	this.options.name = this.options.name || 'Inflicter'
	this.options.context = this.options.context || this.options.name
	this.options.division = this.options.division || this.options.name

	this.options.millieu = this.options.millieu || {}

	this.name = this.options.name
	this.context = this.options.context
	this.division = this.options.division
	this.connectedDivisions = this.options.connectedDivisions || []

	var self = this

	Communication.setupSecurity( self.options.idLength || 16 )

	this.Blower = this.options.Blower || Blower

	self.purifyConfig = { arrayMaxSize: this.options.arrayMaxSize || 100, maxLevel: this.options.maxLevel || 3 }
	self.logger = this.options.logger
	self.logger.harconlog = function ( err, message, obj, level ) {
		this.log( err ? 'error' : (level || 'debug'), err ? (err.message || err.toString()) : message, assigner.assign( purifyObj(obj, self.purifyConfig, 0, []), { 'harcon': VERSION } ) )
	}.bind( self.logger )

	this.Barrel = this.options.Barrel || Barrel
	this.Firestormstarter = this.options.Firestormstarter || Firestormstarter
	this.Flamestarter = this.options.Flamestarter || Flamestarter

	this.barrel = new this.Barrel( )
	var barrelConfig = assigner.assign(
		this.options.barrel || {},
		{ name: this.name, division: this.division, logger: this.logger, connectedDivisions: this.connectedDivisions, igniteLevel: this.options.igniteLevel, seals: this.options.seals }
	)
	this.barrel.init( barrelConfig, function (err) {
		if ( err ) return callback(err)

		self.connectedDivisions.forEach(function ( division ) {
			self.barrel.newDivision( division, function (err) {
				if (err) self.logger.harconlog(err)
			} )
		})

		self.inflicterContext = (self.options.environment || {})
		self.inflicterContext.logger = self.logger

		self.sliceArguments = function ( ) {
			var args = new Array(arguments.length)
			for (var i = 0; i < args.length; i += 1) {
				args[i] = arguments[i]
			}
			return args
		}

		self.addicts( {
			name: 'Inflicter',
			options: self.options,
			Barrel: self.Barrel,
			Blower: self.Blower,
			Firestormstarter: self.Firestormstarter,
			Flamestarter: self.Flamestarter,
			barrel: self.barrel,
			logger: self.logger,
			division: self.division,
			context: self.context,
			detracts: self.detracts,
			addicts: self.addicts,
			addict: self.addict,
			sliceArguments: self.sliceArguments,
			sendNotification: self.sendNotification,
			shifted: self.shifted,
			divisions: self.divisions,
			vivid: function ( cb ) {
				cb( null, 'Hello.' )
			}
		}, {}, function ( err, res ) {
			self.barrel.systemFirestarter = self.systemFirestarter = self.barrel.firestarter( res.name )
			self.logger.harconlog( null, 'Harcon started.', { }, 'info' )

			if ( callback )
				callback(err, res)
		} )
	} )
}

var inflicter = Inflicter.prototype

inflicter.sendNotification = function ( payload, events ) {
	var self = this
	events.forEach( function ( event ) {
		self.ignite( event, payload, function (err) {
			if (err) self.harconlog(err)
		} )
	} )
}

inflicter.shifted = function ( component, callback ) {
	var self = this

	if ( _.isString( component.state ) )
		self.sendNotification( component.state, component.notifyTos[ component.state ] || [] )
	else Object.keys(component.state).forEach( function (key) {
		self.sendNotification( component.state[key], component.notifyTos[ key ] || [] )
	} )

	callback()
}

inflicter.setWarper = function ( division, warper ) {
	return this.barrel.setWarper( division, warper )
}

inflicter.warpers = function () {
	return this.barrel.warpers( )
}

inflicter.divisions = function ( callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		var divisions = self.barrel.divisions( )
		Proback.returner( null, divisions, callback, resolve, reject )
	} )
}

inflicter.entities = function ( division, callback ) {
	var self = this
	if ( _.isFunction(division) ) {
		callback = division
		division = null
	}
	return new Promise( function (resolve, reject) {
		var entities = self.barrel.entities( division )
		Proback.returner( null, entities, callback, resolve, reject )
	} )
}

inflicter.listener = function ( name, callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		var listener = self.barrel.listener( name )
		Proback.returner( null, listener, callback, resolve, reject )
	} )
}

inflicter.pendingComms = function (callback) {
	var self = this
	var comms = self.barrel.firestarters.map( function (fs) {
		return fs.blower
	} ). filter( function (blower) { return !!blower } ).map( function (blower) {
		return blower.comms
	})
	callback( null, comms )
}

/**
* Activates a component
*
* @method activity
* @param {String} name The name of the component to be activated
*/
inflicter.activate = function ( name ) {
	return this.barrel.activity( name, true )
}

/**
* Deactivates a component
*
* @method activity
* @param {String} name The name of the component to be activated
*/
inflicter.deactivate = function ( name ) {
	return this.barrel.activity( name, false )
}

/**
* Unregisters an object-type lister
*
* @method detracts
* @param {Object} object Object
*/
inflicter.detracts = function ( object, callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		self.barrel.castOf( object.name, Proback.handler( callback, resolve, reject ) )
	} )
}

/**
* Registers a new object-type lister
*
* @method addicts
* @param {Object} object Object
*/
inflicter.addicts = function ( object, options, callback ) {
	if ( _.isFunction( options ) ) {
		callback = options
		options = null
	}

	var self = this

	return new Promise( function (resolve, reject) {
		self.detracts( object )
		.then( function () {
			if ( object.division ) {
				if ( object.division !== self.division && !object.division.startsWith( self.division + '.' ) )
					object.division = self.division + '.' + object.division
			}
			else object.division = self.division

			object.inflicterContext = self.inflicterContext
			var blower = new self.Blower( )
			var fss = new self.Firestormstarter( self.options, self.barrel, object, blower, self.logger )
			fss.sliceArguments = self.sliceArguments

			var fns = []
			fns.push( function (cb) {
				blower.init( assigner.assign(self.options.blower || {}, { barrel: self.barrel }), function (err) {
					if ( err ) self.logger.error( err, 'Unable to init Blower' )
					cb( err )
				} )
			} )

			if ( object.init && _.isFunction( object.init ) ) {
				fns.push( function (cb) {
					var componentConfig = assigner.assign( {}, self.options.millieu, self.options[fss.name], options )
					componentConfig.inflicter = self
					try {
						if ( object.init.length === 1 ) {
							object.init( componentConfig )
							cb()
						} else
							object.init( componentConfig, cb )
					} catch ( err ) {
						self.logger.error( err, 'Unable to initialize', fss.name )
						cb( err )
					}
				} )
			}

			fns.push( function (cb) {
				return self.barrel.affiliate( fss, function (err) {
					if ( err )
						self.logger.error( err, 'Unable to affiliate entity', fss.name )
					cb( err, {
						harcon: self, name: fss.name, context: fss.context, division: fss.division, services: fss.services(), rest: !!object.rest, websocket: !!object.websocket
					} )
				} )
			} )

			async.series( fns, function (err, res) {
				if ( err ) return Proback.rejecter( err, callback, reject )
				Proback.resolver( res[ res.length - 1 ], callback, resolve )
			} )
		} )
		.catch( function (reason) {
			Proback.rejecter( reason, callback, reject )
		} )
	})
}

/**
* Registers a new function-type lister
*
* @method addict
* @param {String} division, mandatory
* @param {String} name Name of the listener - needed for logging
* @param {String} eventName Eventname subscription
* @param {Function} fn Listener function
*/
inflicter.addict = function ( division, name, eventName, fn, callback ) {
	var self = this

	return new Promise( function (resolve, reject) {
		var fns = []
		var blower = new self.Blower( )
		fns.push( function (cb) {
			blower.init( self.options.blower || {}, function (err) {
				if ( err ) self.logger.error( err, 'Unable to init Blower' )
				cb( err )
			} )
		} )
		fns.push( function (cb) {
			var flamestarter = new self.Flamestarter( self.options, self.barrel, division || self.division, name, eventName, fn, blower, self.logger )
			flamestarter.sliceArguments = self.sliceArguments
			self.barrel.affiliate( flamestarter, cb )
		} )
		async.series( fns, function (err, res) {
			if ( err ) return Proback.rejecter( err, callback, reject )
			Proback.resolver( res[ res.length - 1 ], callback, resolve )
		} )
	} )
}

/**
* Creates a new event-flow by a starting-event without external ID or division
* The parameter list is a vararg, see parameters below
*
* @method simpleIgnite
* @param {String} event Name of the even to emit, mandatory
* @param {String} params A vararg element possessing the objects to be sent with the message. Can be empty
* @param {Function} callback Mandatory callback function as last element.
*/
inflicter.simpleIgnite = function ( ) {
	var args = [ null, null, this.division ].concat( this.sliceArguments.apply( this, arguments ) )
	return this.systemFirestarter.ignite.apply( this.systemFirestarter, args )
}

/**
* Creates a new event-flow by a starting-event.
* The parameter list is a vararg, see parameters below
*
* @method erupt
* @param {String} external ID, mandatory, can be null
* @param {String} division, mandatory
* @param {String} event Name of the even to emit, mandatory
* @param {String} params A vararg element possessing the objects to be sent with the message. Can be empty
*/
inflicter.erupt = function ( ) {
	return this.systemFirestarter.erupt.apply( this.systemFirestarter, arguments )
}

/**
* Creates a new event-flow by a starting-event.
* The parameter list is a vararg, see parameters below
*
* @method ignite
* @param {String} external ID, mandatory, can be null
* @param {String} division, mandatory
* @param {String} event Name of the even to emit, mandatory
* @param {String} params A vararg element possessing the objects to be sent with the message. Can be empty
* @param {Function} callback Mandatory callback function as last element.
*/
inflicter.ignite = function ( ) {
	return this.systemFirestarter.ignite.apply( this.systemFirestarter, arguments )
}

/**
* Notifies the harcon event-system to close all open connection
*
* @method close
*/
inflicter.close = function ( callback ) {
	var self = this
	return new Promise( function (resolve, reject) {
		self.barrel.close( Proback.handler( callback, resolve, reject ) )
	} )
}

Inflicter.Communication = Communication
Inflicter.Barrel = Barrel
Inflicter.Blower = Blower
Inflicter.Firestormstarter = Firestormstarter
Inflicter.Flamestarter = Flamestarter

module.exports = Inflicter
<|endoftext|>


<commit_before>var fragments = require('fragments');
var hinoki = require('hinoki');

var factories = {
  server: function(
    commonMiddlewarePrelude,
    sequenz
    actionHelloWorld
  ) {
    return sequenz([
      commonMiddlewarePrelude
      actionHelloWorld
    ]);
  },
  actionHelloWorld: function(
    GET
  ) {
    return GET('/hello-world', function(
      end200Text
    ) {
      end200Text('hello-world');
    })
  }
};

var app = fragments([factories, fragments.source]);

app.runCommand();
<commit_msg>update hello world example<commit_after>#!/usr/bin/env node

var factories = {
  server: function(
    commonMiddlewarePrelude,
    sequenz,
    actionHelloWorld,
    notFound
  ) {
    return sequenz([
      commonMiddlewarePrelude,
      actionHelloWorld,
      notFound
    ]);
  },
  notFound: function(
    MIDDLEWARE
  ) {
    return MIDDLEWARE(function(
      endNotFound
    ) {
      endNotFound();
    });
  },
  actionHelloWorld: function(
    GET
  ) {
    return GET('/hello-world', function(
      end200Text
    ) {
      end200Text('hello-world');
    })
  }
};

// below is just configuration boilerplate

var fragments = require('./lib/fragments');
var hinoki = require('hinoki');

var source = hinoki.source([
  factories,
  fragments.source,
  fragments.umgebung
]);

var source = hinoki.decorateSourceToAlsoLookupWithPrefix(source, 'fragments_');

var app = fragments(source);

app.runCommand();
<|endoftext|>


<commit_before>var request = require('request')
  , async = require('async')
  , nfs = require('node-fs')
  , fs = require('fs')
  , lockerUrl
  , auth
  ;

exports.sync = function(processInfo, cb) {
    auth = processInfo.auth;
    auth.headers = {"Authorization":"token "+auth.accessToken, "Connection":"keep-alive"};
    var cached = {};
    if (processInfo.config && processInfo.config.cached)
        cached = processInfo.config.cached;
    lockerUrl = processInfo.lockerUrl;
    exports.syncRepos(cached, function(err, repos) {
        if (err) console.error(err);
        var responseObj = {data : {}, config: { cached: cached }};
        responseObj.data.repo = repos;
        cb(err, responseObj);
    });
};

exports.syncRepos = function(cached, callback) {
    request.get({url:"https://api.github.com/user/repos", json:true, headers:auth.headers}, function(err, resp, repos) {
        if(err || !repos || !repos.length) return callback(err, []);
        // process each one to get richer data
        async.forEachSeries(repos, function(repo, cb) {
            repo.idorig = repo.id;
            repo.id = getIDFromUrl(repo.url); // LEGACY UGH, we're overwriting the numeric id, not simple to change now
            // nothing changed
            var ckey = repo.pushed_at + repo.watchers;
            if(cached[repo.id] == ckey) return cb(); // if it hasn't changed!
            cached[repo.id] = ckey;
            // get the watchers, is nice
            // logger.debug("checking "+repo.id);
            request.get({url:"https://api.github.com/repos/"+repo.id+"/watchers", json:true, headers:auth.headers}, function(err, resp, watchers) {
                repo.watchers = watchers;
                // now see if it's an app special on isle 9
                request.get({uri:'https://raw.github.com/'+repo.id+'/HEAD/package.json', headers:auth.headers}, function(err, resp, pkg) {
                    if(err || !pkg) return cb();
                    try {
                        pkg = JSON.parse(pkg);
                    } catch(E) { return cb(); } // this is going to be really hard to catch, but what can we do from here to let someone know? console.error?
                    var repository = pkg.repository;
                    if(!(repository && (repository.static == "true" || repository.static == true) && repository.type == "app")) return cb();
                    // ok, we've got an app, make sure settings are valid
                    request.get({uri:"https://api.github.com/repos/" + repo.id + "/git/trees/HEAD?recursive=1", headers:auth.headers, json:true}, function(err, resp, tree) {
                        if(err || !tree || !tree.tree) return cb();
                        syncRepo(repo, tree.tree, function(err) {
                            if(err)
                            {
                                delete cached[repo.id]; // invalidate the cache, it never sync'd
                                console.error(err);
                                return cb();
                            }
                            // make sure package.json is safe, set some defaults!
                            pkg.repository.handle = repo.id.replace("/", "-").toLowerCase();
                            pkg.name = pkg.repository.handle;
                            pkg.repository.url = 'https://github.com/' + repo.id;
                            if(!pkg.author) pkg.author = auth.profile.name + ' <'+auth.profile.email+'>';
                            if(!pkg.repository.title) pkg.repository.title = repo.name;
                            if(!pkg.repository.hasOwnProperty('static')) pkg.repository.static = true;
                            if(!pkg.repository.hasOwnProperty('type')) pkg.repository.type = "app";
                            if(!pkg.repository.hasOwnProperty('update')) pkg.repository.update = "auto";
                            fs.writeFileSync(repo.id+"/package.json", JSON.stringify(pkg)); // overwriting :/
                            request.post({url:lockerUrl+'/map/upsert?manifest=Me/github/'+repo.id+'/package.json'}, function(err, resp) {
                                if(err) console.error(err);
                                cb();
                            });
                        });
                    })
                });
            });
        }, function(err){
            callback(err, repos);
        });
    });
}

function getIDFromUrl(url) {
    if(typeof url !== 'string')
        return url;
    return url.substring(url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1);
}

function syncRepo(repo, tree, callback)
{
    var existing = {};
    try {
        var oldtree = JSON.parse(fs.readFileSync(repo.id+".tree.json"));
        for(var i=0; i < oldtree.length; i++)
        {
            existing[oldtree[i].path] = oldtree[i].sha;
        }
    } catch(e){};
    // make sure there's at least one tree entry for the repo dir itself
    tree.push({path:".",sha:"na",type:"tree"});
    async.forEach(tree, function(t, cb){
        if(t.type != "tree") return cb();
        if(existing[t.path] == t.sha) return cb(); // no changes
        nfs.mkdir(repo.id + "/" + t.path, 0777, true, cb);
    }, function(){
        // then the blobs
        var errors = [];
        async.forEachSeries(tree, function(t, cb){
            if(t.type != "blob") return cb();
            if(existing[t.path] == t.sha) return cb(); // no changes
            request.get({uri:'https://raw.github.com/'+repo.id+'/HEAD/'+t.path, encoding: 'binary', headers:auth.headers}, function(err, resp, body) {
                // logger.debug(resp.statusCode + " for "+ t.path);
                if(err || !resp || resp.statusCode != 200) {
                    // don't save the sha so it gets retried again
                    errors.push(repo.id+": error fetching "+t.path+" status code "+(resp ? resp.statusCode : 0)+" body "+body);
                    t.sha = "";
                    return cb();
                }
                if (body) {
                    fs.writeFile(repo.id + "/" + t.path, body, 'binary', cb);
                } else {
                    cb();
                }
            });
        }, function(){
            fs.writeFile(repo.id+".tree.json", JSON.stringify(tree));
            callback(errors.length > 0 ? errors : null);
        });
    });
}
<commit_msg>default version too<commit_after>var request = require('request')
  , async = require('async')
  , nfs = require('node-fs')
  , fs = require('fs')
  , lockerUrl
  , auth
  ;

exports.sync = function(processInfo, cb) {
    auth = processInfo.auth;
    auth.headers = {"Authorization":"token "+auth.accessToken, "Connection":"keep-alive"};
    var cached = {};
    if (processInfo.config && processInfo.config.cached)
        cached = processInfo.config.cached;
    lockerUrl = processInfo.lockerUrl;
    exports.syncRepos(cached, function(err, repos) {
        if (err) console.error(err);
        var responseObj = {data : {}, config: { cached: cached }};
        responseObj.data.repo = repos;
        cb(err, responseObj);
    });
};

exports.syncRepos = function(cached, callback) {
    request.get({url:"https://api.github.com/user/repos", json:true, headers:auth.headers}, function(err, resp, repos) {
        if(err || !repos || !repos.length) return callback(err, []);
        // process each one to get richer data
        async.forEachSeries(repos, function(repo, cb) {
            repo.idorig = repo.id;
            repo.id = getIDFromUrl(repo.url); // LEGACY UGH, we're overwriting the numeric id, not simple to change now
            // nothing changed
            var ckey = repo.pushed_at + repo.watchers;
            if(cached[repo.id] == ckey) return cb(); // if it hasn't changed!
            cached[repo.id] = ckey;
            // get the watchers, is nice
            // logger.debug("checking "+repo.id);
            request.get({url:"https://api.github.com/repos/"+repo.id+"/watchers", json:true, headers:auth.headers}, function(err, resp, watchers) {
                repo.watchers = watchers;
                // now see if it's an app special on isle 9
                request.get({uri:'https://raw.github.com/'+repo.id+'/HEAD/package.json', headers:auth.headers}, function(err, resp, pkg) {
                    if(err || !pkg) return cb();
                    try {
                        pkg = JSON.parse(pkg);
                    } catch(E) { return cb(); } // this is going to be really hard to catch, but what can we do from here to let someone know? console.error?
                    var repository = pkg.repository;
                    if(!(repository && (repository.static == "true" || repository.static == true) && repository.type == "app")) return cb();
                    // ok, we've got an app, make sure settings are valid
                    request.get({uri:"https://api.github.com/repos/" + repo.id + "/git/trees/HEAD?recursive=1", headers:auth.headers, json:true}, function(err, resp, tree) {
                        if(err || !tree || !tree.tree) return cb();
                        syncRepo(repo, tree.tree, function(err) {
                            if(err)
                            {
                                delete cached[repo.id]; // invalidate the cache, it never sync'd
                                console.error(err);
                                return cb();
                            }
                            // make sure package.json is safe, set some defaults!
                            pkg.repository.handle = repo.id.replace("/", "-").toLowerCase();
                            pkg.name = pkg.repository.handle;
                            pkg.repository.url = 'https://github.com/' + repo.id;
                            if(!pkg.author) pkg.author = auth.profile.name + ' <'+auth.profile.email+'>';
                            if(!pkg.version) pkg.version = "0.0.0";
                            if(!pkg.repository.title) pkg.repository.title = repo.name;
                            if(!pkg.repository.hasOwnProperty('static')) pkg.repository.static = true;
                            if(!pkg.repository.hasOwnProperty('type')) pkg.repository.type = "app";
                            if(!pkg.repository.hasOwnProperty('update')) pkg.repository.update = "auto";
                            fs.writeFileSync(repo.id+"/package.json", JSON.stringify(pkg)); // overwriting :/
                            request.post({url:lockerUrl+'/map/upsert?manifest=Me/github/'+repo.id+'/package.json'}, function(err, resp) {
                                if(err) console.error(err);
                                cb();
                            });
                        });
                    })
                });
            });
        }, function(err){
            callback(err, repos);
        });
    });
}

function getIDFromUrl(url) {
    if(typeof url !== 'string')
        return url;
    return url.substring(url.lastIndexOf('/', url.lastIndexOf('/') - 1) + 1);
}

function syncRepo(repo, tree, callback)
{
    var existing = {};
    try {
        var oldtree = JSON.parse(fs.readFileSync(repo.id+".tree.json"));
        for(var i=0; i < oldtree.length; i++)
        {
            existing[oldtree[i].path] = oldtree[i].sha;
        }
    } catch(e){};
    // make sure there's at least one tree entry for the repo dir itself
    tree.push({path:".",sha:"na",type:"tree"});
    async.forEach(tree, function(t, cb){
        if(t.type != "tree") return cb();
        if(existing[t.path] == t.sha) return cb(); // no changes
        nfs.mkdir(repo.id + "/" + t.path, 0777, true, cb);
    }, function(){
        // then the blobs
        var errors = [];
        async.forEachSeries(tree, function(t, cb){
            if(t.type != "blob") return cb();
            if(existing[t.path] == t.sha) return cb(); // no changes
            request.get({uri:'https://raw.github.com/'+repo.id+'/HEAD/'+t.path, encoding: 'binary', headers:auth.headers}, function(err, resp, body) {
                // logger.debug(resp.statusCode + " for "+ t.path);
                if(err || !resp || resp.statusCode != 200) {
                    // don't save the sha so it gets retried again
                    errors.push(repo.id+": error fetching "+t.path+" status code "+(resp ? resp.statusCode : 0)+" body "+body);
                    t.sha = "";
                    return cb();
                }
                if (body) {
                    fs.writeFile(repo.id + "/" + t.path, body, 'binary', cb);
                } else {
                    cb();
                }
            });
        }, function(){
            fs.writeFile(repo.id+".tree.json", JSON.stringify(tree));
            callback(errors.length > 0 ? errors : null);
        });
    });
}
<|endoftext|>


<commit_before>// Load in our dependencies
var fs = require('fs');
var $ = require('jquery');
var assert = require('assert');
var expect = require('chai').expect;
var applicationUtils = require('./utils/application');
var sinonUtils = require('../utils/sinon');
var updateImageSetFilepathResponse = fs.readFileSync(
  __dirname + '/../test-files/http-responses/update-image-set-filepath.json', 'utf8');

// Start our tests
describe('A user accepting failing images is successful', function () {
  // Create our application, set up our XHR mocks, and click our button
  applicationUtils.init();
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 200,
    headers: {'Content-Type': 'application/json'},
    body: updateImageSetFilepathResponse // {imagesEqual: true}
  }]);
  before(function assertBadStatus () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');
  });
  before(function clickAcceptButton (done) {
    // Click our acceptance button
    var buttonEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] button[data-action=accept-changes]');
    assert(buttonEl);
    $(buttonEl).click();

    // Wait for XHR to complete
    setTimeout(done, 100);
  });

  it('updates image set status', function () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('true');
  });

  it('sends XHR to update image to server', function () {
    // Complete initial XHR assertion
    var requests = this.sinonServer.requests;
    expect(requests).to.have.length(1);
    // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
    expect(requests[0].requestBody).to.contain('ref=data');

    // Skip PhantomJS' strange canvas support
    // DEV: Canvas is returning `data:,` only
    if (window.callPhantom) {
      return;
    }

    // Continue deep assertions
    var currentImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=current]');
    var expectedBase64 = applicationUtils.getBase64Content(currentImgEl);
    expect(requests[0].requestBody).to.equal('ref=' + encodeURIComponent(expectedBase64));
  });

  it('cachebusts diff and reference images', function () {
    // Sanity check we don't have loading class
    var diffImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
    var refImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=ref]');
    expect([].slice.call(diffImgEl.classList)).to.not.contain('loading');
    expect([].slice.call(refImgEl.classList)).to.not.contain('loading');

    // Assert new URLs
    // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
    expect(diffImgEl.src).to.match(/\?1/);
    expect(refImgEl.src).to.match(/\?1/);
  });
});

describe('A user accepting failing images has an error', function () {
  // Create our application, silence errors, set up our XHR mocks, and click our button
  applicationUtils.init();
  sinonUtils.stub(console, 'error');
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 500,
    body: 'Internal server error'
  }]);
  before(function assertBadStatus () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');
  });
  before(function clickAcceptButton (done) {
    // Click our acceptance button
    var buttonEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] button[data-action=accept-changes]');
    assert(buttonEl);
    $(buttonEl).click();

    // Wait for XHR to complete
    setTimeout(done, 100);
  });

  it('maintains image set status', function () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');
  });

  it('sends XHR to update image to server', function () {
    // DEV: Other tests asserts XHR thoroughly, this is more of a sanity check
    var requests = this.sinonServer.requests;
    expect(requests).to.have.length(1);
  });

  it('cachebusts diff and reference images', function () {
    // Sanity check we don't have loading class
    var diffImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
    var refImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=ref]');
    expect([].slice.call(diffImgEl.classList)).to.not.contain('loading');
    expect([].slice.call(refImgEl.classList)).to.not.contain('loading');

    // Assert new URLs
    // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
    expect(diffImgEl.src).to.match(/\?1/);
    expect(refImgEl.src).to.match(/\?1/);
  });
});
<commit_msg>Fixed up our curious one-off<commit_after>// Load in our dependencies
var fs = require('fs');
var $ = require('jquery');
var assert = require('assert');
var expect = require('chai').expect;
var sinonUtils = require('../utils/sinon');
// DEV: For unknown reasons, we must import `sinonUtils` before `applicationUtils`
var applicationUtils = require('./utils/application');
var updateImageSetFilepathResponse = fs.readFileSync(
  __dirname + '/../test-files/http-responses/update-image-set-filepath.json', 'utf8');

// Start our tests
describe('A user accepting failing images is successful', function () {
  // Create our application, set up our XHR mocks, and click our button
  applicationUtils.init();
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 200,
    headers: {'Content-Type': 'application/json'},
    body: updateImageSetFilepathResponse // {imagesEqual: true}
  }]);
  before(function assertBadStatus () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');
  });
  before(function clickAcceptButton (done) {
    // Click our acceptance button
    var buttonEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] button[data-action=accept-changes]');
    assert(buttonEl);
    $(buttonEl).click();

    // Wait for XHR to complete
    setTimeout(done, 100);
  });

  it('updates image set status', function () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('true');
  });

  it('sends XHR to update image to server', function () {
    // Complete initial XHR assertion
    var requests = this.sinonServer.requests;
    expect(requests).to.have.length(1);
    // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
    expect(requests[0].requestBody).to.contain('ref=data');

    // Skip PhantomJS' strange canvas support
    // DEV: Canvas is returning `data:,` only
    if (window.callPhantom) {
      return;
    }

    // Continue deep assertions
    var currentImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=current]');
    var expectedBase64 = applicationUtils.getBase64Content(currentImgEl);
    expect(requests[0].requestBody).to.equal('ref=' + encodeURIComponent(expectedBase64));
  });

  it('cachebusts diff and reference images', function () {
    // Sanity check we don't have loading class
    var diffImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
    var refImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=ref]');
    expect([].slice.call(diffImgEl.classList)).to.not.contain('loading');
    expect([].slice.call(refImgEl.classList)).to.not.contain('loading');

    // Assert new URLs
    // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
    expect(diffImgEl.src).to.match(/\?1/);
    expect(refImgEl.src).to.match(/\?1/);
  });
});

describe('A user accepting failing images has an error', function () {
  // Create our application, silence errors, set up our XHR mocks, and click our button
  applicationUtils.init();
  sinonUtils.stub(console, 'error');
  sinonUtils.mockXHR([{
    method: 'POST',
    url: /\/update-image-set\/[^\/]+/,
    statusCode: 500,
    body: 'Internal server error'
  }]);
  before(function assertBadStatus () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');
  });
  before(function clickAcceptButton (done) {
    // Click our acceptance button
    var buttonEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] button[data-action=accept-changes]');
    assert(buttonEl);
    $(buttonEl).click();

    // Wait for XHR to complete
    setTimeout(done, 100);
  });

  it('maintains image set status', function () {
    var imageSetTitleEl = this.containerEl.querySelector('[data-image-set="mock-img-not-equal"] .image-set__title');
    expect(imageSetTitleEl.getAttribute('data-images-equal')).to.equal('false');
  });

  it('sends XHR to update image to server', function () {
    // DEV: Other tests asserts XHR thoroughly, this is more of a sanity check
    var requests = this.sinonServer.requests;
    expect(requests).to.have.length(1);
  });

  it('cachebusts diff and reference images', function () {
    // Sanity check we don't have loading class
    var diffImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=diff]');
    var refImgEl = this.containerEl.querySelector(
      '[data-image-set="mock-img-not-equal"] img[data-compare-type=ref]');
    expect([].slice.call(diffImgEl.classList)).to.not.contain('loading');
    expect([].slice.call(refImgEl.classList)).to.not.contain('loading');

    // Assert new URLs
    // DEV: We don't exclusively compare to the original mock data as they could both be null or similar
    expect(diffImgEl.src).to.match(/\?1/);
    expect(refImgEl.src).to.match(/\?1/);
  });
});
<|endoftext|>