lodash#chain JavaScript Examples

The following examples show how to use lodash#chain. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.js    From datapass with GNU Affero General Public License v3.0 6 votes vote down vote up
getScopesFromEnrollments = (enrollments) =>
  chain(enrollments)
    .map(({ scopes }) =>
      chain(scopes)
        .omitBy((v) => !v)
        .keys()
        .value()
    )
    .flatten()
    .uniq()
    .value()
Example #2
Source File: index.js    From datapass with GNU Affero General Public License v3.0 6 votes vote down vote up
dataProviderParametersToContactInfo = (parameters) =>
  chain(parameters)
    .map(({ label, ...rest }) =>
      label.endsWith(' (Bac à sable)')
        ? { label: label.replace(' (Bac à sable)', ''), ...rest }
        : { label, ...rest }
    )
    .reject(({ email }) => !email)
    .reject(({ label }) => label.endsWith(' (Production)'))
    .reject(({ label }) => label.endsWith(' (FC)'))
    .groupBy(({ email }) => email)
    .mapValues((params) => {
      let labels = params.map(({ label }) => label);
      if (labels.length > 4) {
        labels = labels.slice(0, 4);
        labels.push('etc.');
      }

      return labels.join(', ');
    })
    .toPairs()
    .map(([email, label]) => ({ email, label }))
    .value()
Example #3
Source File: index.js    From datapass with GNU Affero General Public License v3.0 6 votes vote down vote up
getChangelogV1 = (diff) =>
  chain(diff)
    // { intitule: ['a', 'b'], contacts: [[{'name': 'c', email: 'd'}], [{'name': 'e', email: 'd'}]] }
    .omit(['updated_at'])
    .transform(flattenDiffTransformer, {})
    // { intitule: ['a', 'b'], contacts.0.name: ['c', 'e'] }
    .transform(changelogFormatTransformer, [])
    // ['changement d’intitule de "a" en "b"', 'changement du nom du DPD de "c" en "d"']
    .value()
Example #4
Source File: index.js    From datapass with GNU Affero General Public License v3.0 6 votes vote down vote up
export function collectionWithKeyToObject(collection) {
  return (
    chain(collection)
      // [{ id: 'a', attr1: 'a1', attr2: 'a2' }, { id: 'b', attr1: 'b1', attr2: 'b2' }]
      .map(({ id, ...attributes }) => [id, attributes])
      // [[ 'a', { attr1: 'a1', attr2: 'a2' }], ['b', { attr1: 'b1', attr2: 'b2' }]]
      .fromPairs()
      // { a: { attr1: 'a1', attr2: 'a2' },  b: { attr1: 'b1', attr2: 'b2' }}
      .value()
  );
}
Example #5
Source File: index.js    From datapass with GNU Affero General Public License v3.0 6 votes vote down vote up
export function getErrorMessages(error) {
  if (!isEmpty(error.response) && isObject(error.response.data)) {
    return chain(error.response.data).values().flatten().value();
  }

  const errorMessageEnd =
    'Merci de réessayer ultérieurement. ' +
    'Vous pouvez également nous signaler cette erreur par mail à [email protected].';

  if (!isEmpty(error.response) && error.message !== 'Network Error') {
    return [
      `Une erreur est survenue. Le code de l’erreur est ${error.response.status} (${error.response.statusText}). ${errorMessageEnd}`,
    ];
  }

  if (error.message === 'Network Error') {
    return [
      'Une erreur de connexion au serveur est survenue. ' +
        'Merci de vérifier que vous êtes bien connecté à internet. ' +
        'Si vous utilisez un réseau d’entreprise, merci de signaler cette erreur à ' +
        'l’administrateur de votre réseau informatique. ' +
        'Si le problème persiste, vous pouvez nous contacter par mail à ' +
        '[email protected].',
    ];
  }

  console.error(error);
  return [`Une erreur inconnue est survenue. ${errorMessageEnd}`];
}
Example #6
Source File: useNewTeamMembers.js    From datapass with GNU Affero General Public License v3.0 6 votes vote down vote up
useNewTeamMembers = ({
  user,
  team_members,
  contactConfiguration,
}) =>
  useMemo(
    () =>
      chain(contactConfiguration)
        .keys()
        .map((type) => {
          const isMemberAlreadyInitialized = team_members.some(
            ({ type: t }) => t === type
          );
          if (isMemberAlreadyInitialized) {
            return null;
          }

          const tmp_id = uniqueId(`tmp_`);
          let newTeamMember = { type, tmp_id };
          if (type === 'demandeur') {
            newTeamMember = {
              ...newTeamMember,
              email: user.email,
            };
          }
          return newTeamMember;
        })
        .compact()
        .value(),
    [user, team_members, contactConfiguration]
  )
Example #7
Source File: dataProvider.js    From acy-dex-interface with MIT License 5 votes vote down vote up
export function useSwapSources({ from = FIRST_DATE_TS, to = NOW_TS, chainName = "arbitrum" } = {}) {
  const query = `{
    a: ${getSwapSourcesFragment(0, from, to)}
    b: ${getSwapSourcesFragment(1000, from, to)}
    c: ${getSwapSourcesFragment(2000, from, to)}
    d: ${getSwapSourcesFragment(3000, from, to)}
    e: ${getSwapSourcesFragment(4000, from, to)}
  }`
  const [graphData, loading, error] = useGraph(query, { chainName })

  let total = 0
  let data = useMemo(() => {
    if (!graphData) {
      return null
    }

    const {a, b, c, d, e} = graphData
    let ret = chain([...a, ...b, ...c, ...d, ...e])
      .groupBy(item => parseInt(item.timestamp / 86400) * 86400)
      .map((values, timestamp) => {
        let all = 0
        const retItem = {
          timestamp: Number(timestamp),
          ...values.reduce((memo, item) => {
            const source = knownSwapSources[chainName][item.source] || item.source
            if (item.swap != 0) {
              const volume = item.swap / 1e30
              memo[source] = memo[source] || 0
              memo[source] += volume
              all += volume
            }
            return memo
          }, {})
        } 

        retItem.all = all

        return retItem
      })
      .sortBy(item => item.timestamp)
      .value()

    return ret
  }, [graphData])

  return [data, loading, error]
}
Example #8
Source File: resources.js    From file-picker with Apache License 2.0 5 votes vote down vote up
export function buildResource(resource) {
  const ext = resource.type !== 'dir' ? _extName(resource.name) : ''
  return {
    type: resource.type === 'dir' ? 'folder' : resource.type,
    // actual file id (string)
    id: resource.fileInfo['{http://owncloud.org/ns}fileid'],
    // temporary list id, to be used for view only and for uniqueness inside the list
    viewId: uniqueId('file-'),
    starred: resource.fileInfo['{http://owncloud.org/ns}favorite'] !== '0',
    mdate: resource.fileInfo['{DAV:}getlastmodified'],
    size: (function () {
      if (resource.type === 'dir') {
        return resource.fileInfo['{http://owncloud.org/ns}size']
      } else {
        return resource.fileInfo['{DAV:}getcontentlength']
      }
    })(),
    extension: (function () {
      return ext
    })(),
    name: path.basename(resource.name),
    path: resource.name,
    permissions: resource.fileInfo['{http://owncloud.org/ns}permissions'] || '',
    etag: resource.fileInfo['{DAV:}getetag'],
    sharePermissions:
      resource.fileInfo['{http://open-collaboration-services.org/ns}share-permissions'],
    shareTypes: (function () {
      let shareTypes = resource.fileInfo['{http://owncloud.org/ns}share-types']
      if (shareTypes) {
        shareTypes = chain(shareTypes)
          .filter(
            (xmlvalue) =>
              xmlvalue.namespaceURI === 'http://owncloud.org/ns' &&
              xmlvalue.nodeName.split(':')[1] === 'share-type'
          )
          .map((xmlvalue) => parseInt(xmlvalue.textContent || xmlvalue.text, 10))
          .value()
      }
      return shareTypes || []
    })(),
    privateLink: resource.fileInfo['{http://owncloud.org/ns}privatelink'],
    owner: {
      username: resource.fileInfo['{http://owncloud.org/ns}owner-id'],
      displayName: resource.fileInfo['{http://owncloud.org/ns}owner-display-name']
    },
    canUpload: function () {
      return this.permissions.indexOf('C') >= 0
    },
    canDownload: function () {
      return this.type !== 'folder'
    },
    canBeDeleted: function () {
      return this.permissions.indexOf('D') >= 0
    },
    canRename: function () {
      return this.permissions.indexOf('N') >= 0
    },
    canShare: function () {
      return this.permissions.indexOf('R') >= 0
    },
    canCreate: function () {
      return this.permissions.indexOf('C') >= 0
    },
    isMounted: function () {
      return this.permissions.indexOf('M') >= 0
    },
    isReceivedShare: function () {
      return this.permissions.indexOf('S') >= 0
    }
  }
}
Example #9
Source File: index.js    From datapass with GNU Affero General Public License v3.0 5 votes vote down vote up
getChangelogV2 = (diff) =>
  chain(diff)
    // { intitule: ['a', 'b'], team_members: { "_t": "a", "0": { 'name': ['c','e'], email: ['d'] } } }
    .transform(flattenDiffTransformerV2Factory(), {})
    // { intitule: ['a', 'b'], team_members.0.name: ['c', 'e'] , team_members.0.email: ['d'] }
    .transform(changelogFormatTransformer, [])
    // ['changement d’intitule de "a" en "b"', ...]
    .value()
Example #10
Source File: Enrollment.js    From datapass with GNU Affero General Public License v3.0 5 votes vote down vote up
Enrollment = ({
  id,
  events,
  target_api,
  intitule,
  description,
  status,
  onSelect,
}) => {
  const handleClick = useCallback(
    (e) => {
      onSelect(target_api, id, e);
    },
    [id, target_api, onSelect]
  );

  const lastUpdateEvent = useMemo(() => {
    return chain(events)
      .sortBy('updated_at')
      .filter(({ name }) => ['created', 'updated'].includes(name))
      .last()
      .value();
  }, [events]);

  return (
    <div className="enrollment">
      <div className="enrollment-header">
        <div className="fs">
          {DATA_PROVIDER_PARAMETERS[target_api]?.label}{' '}
          {DATA_PROVIDER_PARAMETERS[target_api]?.icon && (
            <div>
              <img
                src={`/images/${DATA_PROVIDER_PARAMETERS[target_api]?.icon}`}
                alt={`logo ${DATA_PROVIDER_PARAMETERS[target_api]?.label}`}
              />
            </div>
          )}
        </div>
        <Tag type={STATUS_TO_BUTTON_TYPE[status]}>
          {USER_STATUS_LABELS[status]}
        </Tag>
      </div>

      <div className="enrollment-body">
        <div className="title">
          <div className="intitule">{intitule || 'Aucun intitulé'}</div>
          {lastUpdateEvent && (
            <div className="creation">
              Dernière modification le{' '}
              <b>{moment(lastUpdateEvent.created_at).format('L')}</b> par{' '}
              <b>{lastUpdateEvent.user.email}</b>
            </div>
          )}
        </div>

        <p className="description">{description || 'Aucune description'}</p>

        <ActivityFeedWrapper
          events={events}
          status={status}
          target_api={target_api}
        />

        <div className="enrollment-footer">
          <div>
            <b>N° {id}</b>
          </div>
          <Button large onClick={handleClick}>
            Consulter
          </Button>
        </div>
      </div>
    </div>
  );
}
Example #11
Source File: ActivityFeed.js    From datapass with GNU Affero General Public License v3.0 5 votes vote down vote up
render() {
    const { showDetails } = this.state;

    const { events } = this.props;

    let eventsToDisplay = chain(events)
      .sortBy('updated_at')
      .reject(
        ({ name, diff }) => name === 'update' && isEmpty(getChangelog(diff))
      )
      .value();

    if (!showDetails && events.length > 0) {
      eventsToDisplay = [last(eventsToDisplay)];
    }

    return (
      <div>
        <div className="activity-head">
          <Button
            outline
            icon="eye"
            onClick={() => this.setState({ showDetails: !showDetails })}
          >
            {showDetails ? 'Cacher l’historique' : 'Voir l’historique'}
          </Button>
        </div>
        {eventsToDisplay.map(
          ({
            id,
            comment,
            name,
            updated_at,
            user: { email, given_name, family_name },
            diff,
          }) => (
            <EventItem
              key={id}
              comment={comment}
              name={name}
              updated_at={updated_at}
              email={email}
              family_name={family_name}
              given_name={given_name}
              diff={diff}
            />
          )
        )}
      </div>
    );
  }
Example #12
Source File: dataProvider.js    From acy-dex-interface with MIT License 5 votes vote down vote up
export function useVolumesData({ from = FIRST_DATE_TS, to = NOW_TS, chainName = "arbitrum" } = {}) {
  const PROPS = 'margin liquidation swap mint burn'.split(' ')
  const feesQuery = `{
    volumeStats(
      first: 1000
      orderBy: id
      orderDirection: desc
      where: { period: daily, id_gte: ${from}, id_lte: ${to} }
    ) {
      id
      margin
      liquidation
      swap
      mint
      burn
      ${chainName === "avalanche" ? "timestamp" : ""}
    }
  }`
  let [feesData, loading, error] = useGraph(feesQuery, {
    chainName
  })

  const feesChartData = useMemo(() => {
    if (!feesData) {
      return null
    }

    let chartData = sortBy(feesData.feeStats, 'id').map(item => {
      const ret = { timestamp: item.timestamp || item.id };

      PROPS.forEach(prop => {
        if (item[prop]) {
          ret[prop] = item[prop] / 1e30
        }
      })

      ret.liquidation = item.liquidation / 1e30 - item.margin / 1e30
      ret.all = PROPS.reduce((memo, prop) => memo + ret[prop], 0)
      return ret
    })

    let cumulative = 0
    const cumulativeByTs = {}
    return chain(chartData)
      .groupBy(item => item.timestamp)
      .map((values, timestamp) => {
        const all = sumBy(values, 'all')
        cumulative += all

        let movingAverageAll
        const movingAverageTs = timestamp - MOVING_AVERAGE_PERIOD
        if (movingAverageTs in cumulativeByTs) {
          movingAverageAll = (cumulative - cumulativeByTs[movingAverageTs]) / MOVING_AVERAGE_DAYS
        }

        const ret = {
          timestamp: Number(timestamp),
          all,
          cumulative,
          movingAverageAll
        }
        PROPS.forEach(prop => {
           ret[prop] = sumBy(values, prop)
        })
        cumulativeByTs[timestamp] = cumulative
        return ret
      })
      .value()
      .filter(item => item.timestamp >= from)
  }, [feesData])
  return [feesChartData, loading, error]
}
Example #13
Source File: dataProvider.js    From acy-dex-interface with MIT License 5 votes vote down vote up
export function useFeesData({ from = FIRST_DATE_TS, to = NOW_TS, chainName = "arbitrum" } = {}) {
  const PROPS = 'margin liquidation swap mint burn'.split(' ')
  const feesQuery = `{
    feeStats(
      first: 1000
      orderBy: id
      orderDirection: desc
      where: { period: daily, id_gte: ${from}, id_lte: ${to} }
    ) {
      id
      margin
      marginAndLiquidation
      swap
      mint
      burn
      ${chainName === "avalanche" ? "timestamp" : ""}
    }
  }`
  let [feesData, loading, error] = useGraph(feesQuery, {
    chainName
  })

  const feesChartData = useMemo(() => {
    if (!feesData) {
      return null
    }

    let chartData = sortBy(feesData.feeStats, 'id').map(item => {
      const ret = { timestamp: item.timestamp || item.id };

      PROPS.forEach(prop => {
        if (item[prop]) {
          ret[prop] = item[prop] / 1e30
        }
      })

      ret.liquidation = item.marginAndLiquidation / 1e30 - item.margin / 1e30
      ret.all = PROPS.reduce((memo, prop) => memo + ret[prop], 0)
      return ret
    })

    let cumulative = 0
    const cumulativeByTs = {}
    return chain(chartData)
      .groupBy(item => item.timestamp)
      .map((values, timestamp) => {
        const all = sumBy(values, 'all')
        cumulative += all

        let movingAverageAll
        const movingAverageTs = timestamp - MOVING_AVERAGE_PERIOD
        if (movingAverageTs in cumulativeByTs) {
          movingAverageAll = (cumulative - cumulativeByTs[movingAverageTs]) / MOVING_AVERAGE_DAYS
        }

        const ret = {
          timestamp: Number(timestamp),
          all,
          cumulative,
          movingAverageAll
        }
        PROPS.forEach(prop => {
           ret[prop] = sumBy(values, prop)
        })
        cumulativeByTs[timestamp] = cumulative
        return ret
      })
      .value()
      .filter(item => item.timestamp >= from)
  }, [feesData])
  return [feesChartData, loading, error]
}
Example #14
Source File: dataProvider.js    From acy-dex-interface with MIT License 5 votes vote down vote up
export function useGambitPoolStats({ from, to, groupPeriod }) {
  const [data, loading, error] = useGraph(`{
    poolStats (
      first: 1000,
      where: { id_gte: ${from}, id_lte: ${to} }
      orderBy: id
      orderDirection: desc
    ) {
      id,
      usdgSupply,
      BTC_usd,
      ETH_usd,
      BNB_usd,
      USDC_usd,
      USDT_usd,
      BUSD_usd
    }
  }`, { subgraph: 'gkrasulya/gambit' })

  const ret = useMemo(() => {
    if (!data) {
       return null
    } 
    let ret = data.poolStats.map(item => {
      return Object.entries(item).reduce((memo, [key, value]) => {
        if (key === 'id') memo.timestamp = value
        else if (key === 'usdgSupply') memo.usdgSupply = value / 1e18
        else memo[key.substr(0, key.length - 4)] = value / 1e30
        return memo
      }, {})
    })

    ret = chain(ret)
      .sortBy('timestamp')
      .groupBy(item => Math.floor(item.timestamp / groupPeriod) * groupPeriod)
      .map((values, timestamp) => {
        return {
          ...values[values.length - 1],
          timestamp
        }
      })
      .value()

     return fillPeriods(ret, { period: groupPeriod, from, to, interpolate: false, extrapolate: true })
  }, [data])

  return [ret, loading, error]
}
Example #15
Source File: index.js    From datapass with GNU Affero General Public License v3.0 4 votes vote down vote up
ÉquipeSection = ({
  initialContacts = {},
  responsableTechniqueNeedsMobilePhone = false,
}) => {
  const {
    isUserEnrollmentLoading,
    disabled,
    onChange,
    enrollment: { team_members = [] },
  } = useContext(FormContext);
  const { user } = useAuth();
  const contactConfiguration = useMemo(() => {
    const defaultInitialContacts = {
      demandeur: {
        header: 'Demandeur',
        description: getDefaultDemandeurDescription(),
        forceDisable: true,
      },
      responsable_traitement: {
        header: 'Responsable de traitement',
        description: getDefaultResponsableTraitementDescription(),
      },
      delegue_protection_donnees: {
        header: 'Délégué à la protection des données',
        description: getDefaultDelegueProtectionDonneesDescription(),
      },
      responsable_technique: {
        header: 'Responsable technique',
        description: getDefaultResponsableTechniqueDescription(
          responsableTechniqueNeedsMobilePhone
        ),
        displayMobilePhoneLabel: responsableTechniqueNeedsMobilePhone,
      },
    };

    return chain(defaultInitialContacts)
      .assign(initialContacts)
      .omitBy((p) => !p)
      .value();
  }, [initialContacts, responsableTechniqueNeedsMobilePhone]);

  const newTeamMembers = useNewTeamMembers({
    user,
    team_members,
    contactConfiguration,
  });

  useEffect(() => {
    if (!isUserEnrollmentLoading && !disabled && !isEmpty(newTeamMembers)) {
      onChange({
        target: {
          name: 'team_members',
          value: [...team_members, ...newTeamMembers],
        },
      });
    }
  }, [
    isUserEnrollmentLoading,
    disabled,
    onChange,
    team_members,
    newTeamMembers,
  ]);

  const displayIdForAdministrator = useMemo(
    () => user && user.roles.includes('administrator'),
    [user]
  );

  const updateWithUserInformation = useCallback(
    (index) => {
      if (team_members[index]?.email !== user.email) {
        onChange({
          target: {
            name: `team_members[${index}].email`,
            value: user.email,
          },
        });
      }
      if (team_members[index]?.given_name !== user.given_name) {
        onChange({
          target: {
            name: `team_members[${index}].given_name`,
            value: user.given_name,
          },
        });
      }
      if (team_members[index]?.family_name !== user.family_name) {
        onChange({
          target: {
            name: `team_members[${index}].family_name`,
            value: user.family_name,
          },
        });
      }
      if (team_members[index]?.phone_number !== user.phone_number) {
        onChange({
          target: {
            name: `team_members[${index}].phone_number`,
            value: user.phone_number,
          },
        });
      }
      if (team_members[index]?.job !== user.job) {
        onChange({
          target: {
            name: `team_members[${index}].job`,
            value: user.job,
          },
        });
      }
    },
    [team_members, user, onChange]
  );

  useEffect(() => {
    if (!isUserEnrollmentLoading && !disabled && !isEmpty(team_members)) {
      const currentDemandeurIndex = team_members.findIndex(
        ({ type, email }) => type === 'demandeur' && email === user.email
      );

      if (currentDemandeurIndex !== -1) {
        updateWithUserInformation(currentDemandeurIndex);
      }
    }
  }, [
    isUserEnrollmentLoading,
    disabled,
    team_members,
    user,
    updateWithUserInformation,
  ]);

  const addTeamMemberFactory = (type) => {
    const tmp_id = uniqueId(`tmp_`);
    const newTeamMember = { type, tmp_id };

    return () =>
      onChange({
        target: {
          name: 'team_members',
          value: [...team_members, newTeamMember],
        },
      });
  };

  const removeTeamMember = (index) => {
    onChange({
      target: {
        name: 'team_members',
        value: [
          ...team_members.slice(0, index),
          ...team_members.slice(index + 1),
        ],
      },
    });
  };

  return (
    <ScrollablePanel scrollableId={SECTION_ID}>
      <h2>{SECTION_LABEL}</h2>
      <ExpandableQuote title="Comment renseigner la liste des contacts ?" large>
        {Object.entries(contactConfiguration).map(([type, { description }]) => (
          <p key={type}>{description}</p>
        ))}
      </ExpandableQuote>
      <CardContainer flex={false}>
        {Object.entries(contactConfiguration).map(
          ([
            type,
            {
              header,
              forceDisable,
              displayMobilePhoneLabel,
              displayIndividualEmailLabel,
              displayGroupEmailLabel,
              contactByEmailOnly,
              multiple,
            },
          ]) => (
            <React.Fragment key={type}>
              {team_members
                .filter(({ type: t }) => t === type)
                .map(({ id, tmp_id, ...team_member }) => (
                  <Contact
                    heading={header}
                    key={id || tmp_id}
                    id={id}
                    index={findIndex(team_members, ({ id: i, tmp_id: t_i }) => {
                      if (id) {
                        // if id is defined match on id field
                        return i === id;
                      }
                      if (tmp_id) {
                        // if id is not defined and tmp_id is defined
                        // match on tmp_id
                        return t_i === tmp_id;
                      }
                      return false;
                    })}
                    {...team_member}
                    displayMobilePhoneLabel={displayMobilePhoneLabel}
                    displayIndividualEmailLabel={displayIndividualEmailLabel}
                    displayGroupEmailLabel={displayGroupEmailLabel}
                    contactByEmailOnly={contactByEmailOnly}
                    displayIdForAdministrator={displayIdForAdministrator}
                    disabled={forceDisable || disabled}
                    onChange={onChange}
                    onDelete={multiple && !id && removeTeamMember}
                    onUpdateWithUserInformation={updateWithUserInformation}
                    canUpdatePersonalInformation={
                      team_member.email === user.email &&
                      (team_member.given_name !== user.given_name ||
                        team_member.family_name !== user.family_name ||
                        team_member.phone_number !== user.phone_number ||
                        team_member.job !== user.job)
                    }
                  />
                ))}
              {!disabled && multiple && (
                <AddCard
                  label={`ajouter un ${header.toLowerCase()}`}
                  onClick={addTeamMemberFactory(type)}
                />
              )}
            </React.Fragment>
          )
        )}
      </CardContainer>
    </ScrollablePanel>
  );
}
Example #16
Source File: ActivityFeedWrapper.js    From datapass with GNU Affero General Public License v3.0 4 votes vote down vote up
ActivityFeedWrapper = ({ events, status, target_api }) => {
  const [
    majorityPercentileProcessingTimeInDays,
    setMajorityPercentileTimeInDays,
  ] = useState(0);

  useEffect(() => {
    async function fetchStats() {
      const {
        data: { majority_percentile_processing_time_in_days },
      } = await getCachedMajorityPercentileProcessingTimeInDays(target_api);
      setMajorityPercentileTimeInDays(
        majority_percentile_processing_time_in_days
      );
    }

    if (status === 'submitted') {
      fetchStats();
    }
  }, [status, target_api]);

  const {
    comment = '',
    name: lastEventName = '',
    updated_at = '',
    user: { email = '', given_name, family_name } = {},
    diff,
  } = useMemo(
    () => chain(events).sortBy('updated_at').last().value() || {},
    [events]
  );

  if (
    ['draft', 'changes_requested'].includes(status) &&
    ['request_changes'].includes(lastEventName)
  ) {
    return (
      <Alert title={USER_STATUS_LABELS[status]} type="warning">
        Votre demande d’habilitation est incomplète et requiert les
        modifications suivantes :
        <div style={{ margin: '1em 0' }}>
          <EventItem
            comment={comment}
            name={lastEventName}
            updated_at={updated_at}
            email={email}
            given_name={given_name}
            family_name={family_name}
            diff={diff}
          />
        </div>
      </Alert>
    );
  }

  if (
    ['draft', 'changes_requested'].includes(status) &&
    ['create', 'update'].includes(lastEventName)
  ) {
    return (
      <Alert title={USER_STATUS_LABELS[status]}>
        <p>
          Votre demande d’habilitation est actuellement en cours d’édition.
          Notre service juridique pourra la consulter lorsque vous cliquerez sur
          "Soumettre la demande d’habilitation".
        </p>
      </Alert>
    );
  }

  if (status === 'submitted' && majorityPercentileProcessingTimeInDays > 0) {
    return (
      <Alert title={USER_STATUS_LABELS[status]}>
        La majorité des demandes d’habilitation des 6 derniers mois reçoivent
        une réponse en moins de{' '}
        <b>{majorityPercentileProcessingTimeInDays} jours</b>.
      </Alert>
    );
  }

  return null;
}
Example #17
Source File: index.js    From datapass with GNU Affero General Public License v3.0 4 votes vote down vote up
DonneesSection = ({
  DonneesDescription,
  availableScopes = [],
  AvailableScopesDescription,
  accessModes,
  enableFileSubmissionForScopeSelection = false,
}) => {
  const {
    disabled,
    onChange,
    enrollment: {
      scopes = {},
      data_recipients = '',
      data_retention_period = '',
      data_retention_comment = '',
      additional_content = {},
      documents = [],
      documents_attributes = [],
    },
  } = useContext(FormContext);

  useEffect(() => {
    if (!isEmpty(availableScopes) && isEmpty(scopes)) {
      onChange({
        target: {
          name: 'scopes',
          value: zipObject(
            availableScopes.map(({ value }) => value),
            availableScopes.map(
              ({ required, checkedByDefault }) =>
                !!required || !!checkedByDefault
            )
          ),
        },
      });
    }
  });

  const groupTitleScopesGroup = groupBy(
    availableScopes,
    (e) => e.groupTitle || 'default'
  );

  // {'a': true, 'b': false, 'c': true} becomes ['a', 'c']
  const scopesAsArray = chain(scopes)
    .omitBy((e) => !e)
    .keys()
    .value();
  const availableScopesAsArray = availableScopes.map(({ value }) => value);
  const outdatedScopes = difference(scopesAsArray, availableScopesAsArray);

  const [isFileInputExpanded, setFileInputExpanded] = useState(
    enableFileSubmissionForScopeSelection &&
      !isEmpty(
        documents.filter(
          ({ type }) => type === 'Document::ExpressionBesoinSpecifique'
        )
      )
  );

  useEffect(() => {
    const hasDocument = !isEmpty(
      documents.filter(
        ({ type }) => type === 'Document::ExpressionBesoinSpecifique'
      )
    );
    if (
      enableFileSubmissionForScopeSelection &&
      !isFileInputExpanded &&
      hasDocument
    ) {
      setFileInputExpanded(true);
    }
  }, [enableFileSubmissionForScopeSelection, isFileInputExpanded, documents]);

  return (
    <ScrollablePanel scrollableId={SECTION_ID}>
      <h2>Les données nécessaires</h2>
      {DonneesDescription && (
        <ExpandableQuote title="Comment choisir les données ?">
          <DonneesDescription />
        </ExpandableQuote>
      )}
      {AvailableScopesDescription && <AvailableScopesDescription />}
      {!isEmpty(availableScopes) && (
        <>
          <h3>À quelles données souhaitez-vous avoir accès ?</h3>
          {Object.keys(groupTitleScopesGroup).map((group) => (
            <Scopes
              key={group}
              title={group === 'default' ? null : group}
              scopes={groupTitleScopesGroup[group]}
              selectedScopes={scopes}
              disabledApplication={disabled}
              handleChange={onChange}
            />
          ))}
          {disabled && !isEmpty(outdatedScopes) && (
            <Scopes
              title="Les données suivantes ont été sélectionnées mais ne sont plus disponibles :"
              scopes={outdatedScopes.map((value) => ({ value, label: value }))}
              selectedScopes={zipObject(
                outdatedScopes,
                Array(outdatedScopes.length).fill(true)
              )}
              disabledApplication
              handleChange={() => null}
            />
          )}
        </>
      )}
      {enableFileSubmissionForScopeSelection && (
        <>
          <ExpandableQuote title="J’ai une expression de besoin spécifique ?">
            <p>
              Les partenaires ayant convenu avec la DGFiP un périmètre de
              données particulier peuvent rattacher leur expression de besoin
              listant l’ensemble des données strictement nécessaires à leur cas
              d’usage. Si vous n'avez pas encore contacté la DGFiP, vous pouvez
              les joindre à l'adresse{' '}
              <Link
                inline
                href="mailto:[email protected]?subject=Expression%20de%20besoin%20spécifique"
              >
                [email protected]
              </Link>
            </p>
            <CheckboxInput
              label="J’ai une expression de besoin spécifique"
              value={isFileInputExpanded}
              onChange={() => setFileInputExpanded(!isFileInputExpanded)}
              disabled={disabled}
            />
          </ExpandableQuote>
          {isFileInputExpanded && (
            <>
              <FileInput
                label="Joindre l’expression de besoin"
                meta={
                  'Attention : seule l’expression de besoin en données ayant déjà été partagée avec la DGFiP peut être rattachée à votre habilitation.'
                }
                mimeTypes="*"
                disabled={disabled}
                uploadedDocuments={documents}
                documentsToUpload={documents_attributes}
                documentType={'Document::ExpressionBesoinSpecifique'}
                onChange={onChange}
              />
            </>
          )}
        </>
      )}
      {!isEmpty(accessModes) && (
        <>
          <h3>Comment souhaitez-vous y accéder ?</h3>
          {accessModes.map(({ id, label }) => (
            <CheckboxInput
              key={id}
              label={label}
              name={`additional_content.${id}`}
              value={additional_content[id] || false}
              disabled={disabled}
              onChange={onChange}
            />
          ))}
        </>
      )}
      <h3>Comment seront traitées ces données personnelles ?</h3>
      <TextInput
        label="Destinataires des données"
        placeholder={
          '« agents instructeurs des demandes d’aides », « usagers des ' +
          'services publics de la ville », etc.'
        }
        meta={
          <Link
            inline
            newTab
            href="https://www.cnil.fr/fr/definition/destinataire"
            aria-label="Voir la définition CNIL du destinataire des données"
          >
            Plus d’infos
          </Link>
        }
        name="data_recipients"
        value={data_recipients}
        disabled={disabled}
        onChange={onChange}
        required
      />
      <NumberInput
        label="Durée de conservation des données en mois"
        meta={
          <Link
            inline
            newTab
            href="https://www.cnil.fr/fr/les-durees-de-conservation-des-donnees"
            aria-label="Voir l’explication CNIL sur les durées de conservation des données"
          >
            Plus d’infos
          </Link>
        }
        name="data_retention_period"
        value={data_retention_period}
        disabled={disabled}
        onChange={onChange}
        required
      />
      {data_retention_period > 36 && (
        <>
          <Alert type="warning" title="Attention">
            Cette durée excède la durée communément constatée (36 mois).
          </Alert>
          <TextInput
            label="Veuillez justifier cette durée dans le champ ci-après :"
            name="data_retention_comment"
            value={data_retention_comment}
            disabled={disabled}
            onChange={onChange}
          />
        </>
      )}
    </ScrollablePanel>
  );
}
Example #18
Source File: index.js    From datapass with GNU Affero General Public License v3.0 4 votes vote down vote up
TextInputWithSuggestions = ({
  label,
  name,
  options = [],
  value,
  disabled,
  onChange,
  required,
}) => {
  // id will be set once when the component initially renders, but never again
  // we generate an unique id prefixed by the field name
  const [id] = useState(uniqueId(name));

  const [suggestions, setSuggestions] = useState([]);

  // from https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
  const normalize = (string = '') =>
    string
      .toLowerCase()
      .normalize('NFD')
      .replace(/\p{Diacritic}/gu, '')
      .replace(/[^\w\s]/gi, ' ');

  useEffect(() => {
    const newSuggestions = chain(options)
      .map(({ id, label }) => ({
        id,
        label,
        distance: levenshtein(normalize(value), normalize(label)).similarity,
      }))
      .sortBy(['distance'])
      .reverse()
      .filter(({ distance }) => distance > 0.25)
      .take(10)
      .value();

    setSuggestions(newSuggestions);
  }, [options, value]);

  const [isDropDownOpen, setIsDropDownOpen] = useState(false);
  const [activeSuggestion, setActiveSuggestion] = useState(null);

  const closeDropDown = () => {
    setIsDropDownOpen(false);
    setActiveSuggestion(null);
  };

  const onKeyDown = (e) => {
    if (e.key === 'Tab' && isDropDownOpen) {
      e.preventDefault();
      closeDropDown();
    }

    if (e.key === 'Enter' && !isDropDownOpen) {
      e.preventDefault();
      setIsDropDownOpen(true);
    }

    if (e.key === 'Escape' && isDropDownOpen) {
      e.preventDefault();
      closeDropDown();
    }

    if (e.key === 'ArrowDown' && !isDropDownOpen) {
      e.preventDefault();
      setIsDropDownOpen(true);
      setActiveSuggestion(0);
    }

    if (e.key === 'ArrowDown' && isDropDownOpen && isNull(activeSuggestion)) {
      e.preventDefault();
      setActiveSuggestion(0);
    }

    if (e.key === 'ArrowDown' && isDropDownOpen && isNumber(activeSuggestion)) {
      e.preventDefault();
      setActiveSuggestion(min([activeSuggestion + 1, suggestions.length - 1]));
    }

    if (e.key === 'ArrowUp' && isDropDownOpen) {
      e.preventDefault();
      setActiveSuggestion(max([activeSuggestion - 1, 0]));
    }

    if (e.key === 'Enter' && isDropDownOpen) {
      e.preventDefault();
      onChange({
        target: { name, value: suggestions[activeSuggestion]?.label },
      });
      closeDropDown();
    }
  };

  const handleChange = (value) => {
    closeDropDown();
    onChange({ target: { name, value } });
  };

  return (
    <div className="fr-input-group">
      <label htmlFor={id}>
        {label}
        {required && ' *'}
      </label>
      <input
        className="fr-input"
        type="text"
        onChange={onChange}
        name={name}
        id={id}
        readOnly={disabled}
        value={value}
        required={required}
        onKeyDown={onKeyDown}
        onClick={() => setIsDropDownOpen(true)}
        onInput={() => setIsDropDownOpen(true)}
      />
      {!disabled && isDropDownOpen && !isEmpty(suggestions) && (
        <Dropdown onOutsideClick={closeDropDown} fillWidth>
          {suggestions.map(({ id, label }, index) => (
            <div
              key={id}
              className={`datapass-text-input-suggestion ${
                activeSuggestion === index
                  ? 'datapass-text-input-active-suggestion'
                  : ''
              }`}
              onClick={() => handleChange(label)}
            >
              {label}
            </div>
          ))}
        </Dropdown>
      )}
    </div>
  );
}
Example #19
Source File: diff.js    From hivemind with Apache License 2.0 4 votes vote down vote up
DiffAPI = async (req, res) => {
  const { token } = req.headers

  try {
    const claims = await verifyIdToken(token)
    const ukey = claims.uid
    const userId = `users/${ukey}`
    const { eid } = req.query
    const cEvent = await compoundEvents.document(eid)
    const { mid, event, fctime, lctime, nids, eids } = cEvent

    if (await hasReadAccess(mid, userId)) {
      let response, diff, nid, output, indexes, filteredNids, nKeys, path, timestamp

      switch (req.method) {
        case 'GET':
          output = {}

          switch (event) {
            case 'created':
            case 'restored':
              output.v1 = {}
              nid = nids.find((nid) => nid.startsWith('nodes/')) || mid
              response = await rg.get('/history/show', {
                path: `/n/${nid}`,
                timestamp: lctime,
              })
              output.v2 = response.body[0]

              break

            case 'deleted':
              indexes = chain(nids)
                .map((nid, idx) =>
                  /^(nodes|mindmaps)\//.test(nid) ? idx : null
                )
                .reject(isNull)
                .value()
              output.v2 = indexes.map(() => ({}))
              filteredNids = nids.filter((nid, idx) =>
                indexes.includes(idx)
              )

              nKeys = {}
              for (const nid of filteredNids) {
                const [coll, key] = nid.split('/')
                if (!nKeys[coll]) {
                  nKeys[coll] = []
                }
                nKeys[coll].push(key)
              }
              path = createNodeBracePath(nKeys)

              timestamp = fctime - 0.0001
              response = await rg.get('/history/show', { path, timestamp })
              output.v1 = response.body

              break

            case 'updated':
              nid = nids[0]
              response = await rg.get('/history/show', {
                path: `/n/${nid}`,
                timestamp: lctime,
              })
              output.v2 = response.body[0]

              diff = await getReversedDiff(nid, eids[0], fctime, lctime)
              output.v1 = patch(diff, response.body[0])
          }

          return res.status(200).json(output)
      }
    } else {
      return res.status(401).json({ message: 'Access Denied.' })
    }
  } catch (error) {
    console.error(error.message, error.stack)

    return res.status(401).json({ message: 'Access Denied.' })
  }
}
Example #20
Source File: index.js    From hivemind with Apache License 2.0 4 votes vote down vote up
MindMapsAPI = async (req, res) => {
  const { token } = req.headers

  try {
    const claims = await verifyIdToken(token)
    const key = claims.uid
    const userId = `users/${key}`

    let mindmap, response, message, query, cursor, mindmaps, name

    switch (req.method) {
      case 'GET':
        query = aql`
          for v, e in 1
          outbound ${userId}
          graph 'mindmaps'
          
          return { mindmap: v, access: e }
        `
        cursor = await db.query(query)
        mindmaps = await cursor.all()

        return res.status(200).json(mindmaps)

      case 'POST':
        name = req.body.name
        mindmap = {
          name,
          isRoot: true,
          title: name,
          createdBy: userId,
        }
        response = await rg.post('/document/mindmaps', mindmap)

        if (response.statusCode === 201) {
          mindmap = response.body

          const access = {
            _from: `users/${key}`,
            _to: mindmap._id,
            access: 'admin',
          }
          response = await rg.post('/document/access', access)
          if (response.statusCode === 201) {
            message = 'Mindmap created.'

            await recordCompoundEvent('created', userId, [mindmap])
          } else {
            message = response.body

            await rg.delete(
              '/history/purge',
              { path: `/n/${mindmap._id}` },
              { silent: true, deleteUserObjects: true }
            )
          }
        } else {
          message = response.body
        }

        return res.status(response.statusCode).json({ message })

      case 'PATCH':
        if (isEmpty(req.body._id)) {
          return res.status(400).json({ message: 'Mindmap id is required' })
        }

        if (await hasWriteAccess(req.body._id, userId)) {
          mindmap = chain(req.body)
            .pick('audio', 'summary', 'content', '_rev', '_id', 'title', 'name')
            .omitBy(isNil)
            .value()
          mindmap.lastUpdatedBy = userId

          response = await rg.patch('/document/mindmaps', mindmap, {
            keepNull: false,
            ignoreRevs: false,
          })

          await recordCompoundEvent('updated', userId, [response.body])

          return res.status(response.statusCode).json(response.body)
        } else {
          return res.status(401).json({ message: 'Access Denied.' })
        }
    }
  } catch (error) {
    console.error(error.message, error.stack)
    return res.status(401).json({ message: 'Access Denied.' })
  }
}
Example #21
Source File: index.js    From hivemind with Apache License 2.0 4 votes vote down vote up
MindMapAPI = async (req, res) => {
  const { token } = req.headers

  try {
    const claims = await verifyIdToken(token)
    const ukey = claims.uid
    const userId = `users/${ukey}`
    const { key, timestamp } = req.query
    const id = `mindmaps/${key}`

    let mindmap, query, cursor, edgeStartIdx

    switch (req.method) {
      case 'GET':
        if (timestamp && (await hasReadAccess(id, userId))) {
          const response = await rg.post(
            '/history/traverse',
            {
              edges: { access: 'outbound', links: 'outbound' },
            },
            {
              timestamp,
              svid: id,
              minDepth: 0,
              maxDepth: Number.MAX_SAFE_INTEGER,
              uniqueVertices: 'global',
              returnPaths: false,
            }
          )

          mindmap = response.body
          edgeStartIdx = 0
        } else {
          query = aql`
            for v, e, p in 1..${Number.MAX_SAFE_INTEGER}
            any ${userId}
            graph 'mindmaps'
            
            options { uniqueVertices: 'global', bfs: true }
            
            filter p.vertices[1]._id == ${id}
            
            collect aggregate vertices = unique(v), edges = unique(e)
            
            return { vertices, edges }
          `
          cursor = await db.query(query)
          mindmap = await cursor.next()
          edgeStartIdx = 1
        }

        if (get(mindmap, ['vertices', 'length'])) {
          const meta = mindmap.vertices[0]
          const access = mindmap.edges[0]
          const vertices = [],
            edges = []

          for (let i = 0; i < mindmap.vertices.length; i++) {
            vertices.push(mindmap.vertices[i])
          }
          for (let i = edgeStartIdx; i < mindmap.edges.length; i++) {
            edges.push(mindmap.edges[i])
          }

          const userIds = chain(vertices)
            .flatMap((v) => [v.createdBy, v.lastUpdatedBy])
            .compact()
            .uniq()
            .value()
          query = aql`
            for u in users
            filter u._id in ${userIds}
            
            return keep(u, '_id', 'displayName', 'email')
          `
          cursor = await db.query(query)
          const users = await cursor.all()

          for (const v of vertices) {
            for (const field of ['createdBy', 'lastUpdatedBy']) {
              if (v[field]) {
                const user = users.find((u) => u._id === v[field])
                v[field] = user.displayName || user.email
              }
            }
          }

          const result = {
            meta,
            access,
            elements: rg2cy([
              {
                type: 'vertex',
                nodes: vertices,
              },
              {
                type: 'edge',
                nodes: edges,
              },
            ]),
          }

          return res.status(200).json(result)
        }

        return res.status(401).json({ message: 'Access Denied.' })

      default:
        return res.status(405).json({ message: 'Method Not Allowed' })
    }
  } catch (error) {
    console.error(error.message, error.stack)
    return res.status(401).json({ message: 'Access Denied.' })
  }
}