diff --git a/package-lock.json b/package-lock.json index 6a03cfa2f4..dc04fc193e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1134,6 +1134,87 @@ } } }, + "@emotion/cache": { + "version": "10.0.15", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.15.tgz", + "integrity": "sha512-8VthgeKhlGeTXSW1JN7I14AnAaiFPbOrqNqg3dPoGCZ3bnMjkrmRU0zrx0BtBw9esBaPaQgDB9y0tVgAGT2Mrg==", + "requires": { + "@emotion/sheet": "0.9.3", + "@emotion/stylis": "0.8.4", + "@emotion/utils": "0.11.2", + "@emotion/weak-memoize": "0.2.3" + } + }, + "@emotion/core": { + "version": "10.0.16", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.16.tgz", + "integrity": "sha512-whbiiA7FfPreBY4BqWky2qRfAZvq+4dKQ1WNJuiYQwPCNmb0pEYDgNheSbZoNKtGTtfPaM28hBbZAKWD5EZXmQ==", + "requires": { + "@babel/runtime": "^7.4.3", + "@emotion/cache": "^10.0.15", + "@emotion/css": "^10.0.14", + "@emotion/serialize": "^0.11.9", + "@emotion/sheet": "0.9.3", + "@emotion/utils": "0.11.2" + } + }, + "@emotion/css": { + "version": "10.0.14", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.14.tgz", + "integrity": "sha512-MozgPkBEWvorcdpqHZE5x1D/PLEHUitALQCQYt2wayf4UNhpgQs2tN0UwHYS4FMy5ROBH+0ALyCFVYJ/ywmwlg==", + "requires": { + "@emotion/serialize": "^0.11.8", + "@emotion/utils": "0.11.2", + "babel-plugin-emotion": "^10.0.14" + } + }, + "@emotion/hash": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.2.tgz", + "integrity": "sha512-RMtr1i6E8MXaBWwhXL3yeOU8JXRnz8GNxHvaUfVvwxokvayUY0zoBeWbKw1S9XkufmGEEdQd228pSZXFkAln8Q==" + }, + "@emotion/memoize": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.2.tgz", + "integrity": "sha512-hnHhwQzvPCW1QjBWFyBtsETdllOM92BfrKWbUTmh9aeOlcVOiXvlPsK4104xH8NsaKfg86PTFsWkueQeUfMA/w==" + }, + "@emotion/serialize": { + "version": "0.11.9", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.9.tgz", + "integrity": "sha512-/Cn4V81z3ZyFiDQRw8nhGFaHkxHtmCSSBUit4vgTuLA1BqxfJUYiqSq97tq/vV8z9LfIoqs6a9v6QrUFWZpK7A==", + "requires": { + "@emotion/hash": "0.7.2", + "@emotion/memoize": "0.7.2", + "@emotion/unitless": "0.7.4", + "@emotion/utils": "0.11.2", + "csstype": "^2.5.7" + } + }, + "@emotion/sheet": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.3.tgz", + "integrity": "sha512-c3Q6V7Df7jfwSq5AzQWbXHa5soeE4F5cbqi40xn0CzXxWW9/6Mxq48WJEtqfWzbZtW9odZdnRAkwCQwN12ob4A==" + }, + "@emotion/stylis": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.4.tgz", + "integrity": "sha512-TLmkCVm8f8gH0oLv+HWKiu7e8xmBIaokhxcEKPh1m8pXiV/akCiq50FvYgOwY42rjejck8nsdQxZlXZ7pmyBUQ==" + }, + "@emotion/unitless": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.4.tgz", + "integrity": "sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ==" + }, + "@emotion/utils": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.2.tgz", + "integrity": "sha512-UHX2XklLl3sIaP6oiMmlVzT0J+2ATTVpf0dHQVyPJHTkOITvXfaSqnRk6mdDhV9pR8T/tHc3cex78IKXssmzrA==" + }, + "@emotion/weak-memoize": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.3.tgz", + "integrity": "sha512-zVgvPwGK7c1aVdUVc9Qv7SqepOGRDrqCw7KZPSZziWGxSlbII3gmvGLPzLX4d0n0BMbamBacUrN22zOMyFFEkQ==" + }, "@formatjs/intl-relativetimeformat": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-2.6.3.tgz", @@ -3563,6 +3644,23 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-emotion": { + "version": "10.0.16", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.0.16.tgz", + "integrity": "sha512-a01Xrourr/VRpw4KicX9drDwfVGHmw8HmlQk++N4fv0j73EfHKWC1Ah4Vu8s1cTGVvTiwum+UhVpJenV8j03FQ==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.7.2", + "@emotion/memoize": "0.7.2", + "@emotion/serialize": "^0.11.9", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + } + }, "babel-plugin-import-graphql": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/babel-plugin-import-graphql/-/babel-plugin-import-graphql-2.7.0.tgz", @@ -3663,6 +3761,16 @@ "require-package-name": "^2.0.1" } }, + "babel-plugin-macros": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz", + "integrity": "sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ==", + "requires": { + "@babel/runtime": "^7.4.2", + "cosmiconfig": "^5.2.0", + "resolve": "^1.10.0" + } + }, "babel-plugin-react-intl": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/babel-plugin-react-intl/-/babel-plugin-react-intl-4.1.12.tgz", @@ -8071,6 +8179,11 @@ "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=" }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -17905,6 +18018,15 @@ } } }, + "react-toast-notifications": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/react-toast-notifications/-/react-toast-notifications-2.2.5.tgz", + "integrity": "sha512-Z2oZK2NUHZKaFI2fFTP5Byq3EFJ5Ufa0NHBISMr7l3zfAy4TCMwyTyxgE/PHnpYV204XPoi8s+/bSeHCP/atXA==", + "requires": { + "@emotion/core": "^10.0.14", + "react-transition-group": "^2.3.1" + } + }, "react-tooltip": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-3.10.0.tgz", diff --git a/package.json b/package.json index f403f19775..b8fa8c8f7e 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "react-intl": "3.1.1", "react-rangeslider": "2.2.0", "react-sizeme": "2.6.7", + "react-toast-notifications": "2.2.5", "react-tooltip": "3.10.0", "recharts": "1.7.1", "sanitize-html": "1.20.1", diff --git a/src/components/questions/ActionBar.tsx b/src/components/questions/ActionBar.tsx index 13f27d7e1a..472adb3ebf 100644 --- a/src/components/questions/ActionBar.tsx +++ b/src/components/questions/ActionBar.tsx @@ -1,7 +1,13 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import Link from 'next/link' +import _sortBy from 'lodash/sortBy' +import { CSVDownload } from 'react-csv' +import { useToasts } from 'react-toast-notifications' import { defineMessages, FormattedMessage, useIntl } from 'react-intl' -import { Button, Confirm, Icon } from 'semantic-ui-react' +import { Button, Confirm, Icon, Label, Feed } from 'semantic-ui-react' +import { useMutation } from '@apollo/react-hooks' + +import QuestionStatisticsMutation from '../../graphql/mutations/QuestionStatisticsMutation.graphql' const messages = defineMessages({ deletionConfirmationCancel: { @@ -28,13 +34,13 @@ interface Props { handleQuickBlock: any handleQuickBlocks: any isArchiveActive?: boolean - itemsChecked?: number + itemsChecked?: string[] } const defaultProps = { creationMode: false, isArchiveActive: false, - itemsChecked: 0, + itemsChecked: [], } function ActionBar({ @@ -49,6 +55,70 @@ function ActionBar({ handleQuickBlocks, }: Props): React.ReactElement { const intl = useIntl() + const { addToast } = useToasts() + const [csvData, setCsvData] = useState() + + const [getQuestionStatistics, { data, error, loading }] = useMutation(QuestionStatisticsMutation) + + useEffect((): void => { + if (data) { + const versionResults = {} + + data.questionStatistics.forEach(({ id, title, type, usageTotal, usageDetails, statistics }) => { + usageDetails.forEach(({ version, count }) => { + versionResults[`${id}_v${version}`] = { + _title: title, + _type: type, + _question: id, + _usageTotal: usageTotal, + _usageVersion: count, + _version: version, + } + }) + + statistics.forEach(({ version, CHOICES, FREE }) => { + if (type === 'SC' || type === 'MC') { + _sortBy(CHOICES, choice => choice.chosen) + .reverse() + .forEach(({ name, correct, chosen, total, percentageChosen }, ix) => { + versionResults[`${id}_v${version}`][`c${ix}_name`] = name + versionResults[`${id}_v${version}`][`c${ix}_correct`] = correct + versionResults[`${id}_v${version}`][`c${ix}_chosen`] = chosen + versionResults[`${id}_v${version}`][`c${ix}_total`] = total + versionResults[`${id}_v${version}`][`c${ix}_percentageChosen`] = percentageChosen + }) + } else if (type === 'FREE' || type === 'FREE_RANGE') { + _sortBy(FREE, free => free.chosen) + .reverse() + .forEach(({ value, chosen, total, percentageChosen }, ix) => { + versionResults[`${id}_v${version}`][`f${ix}_value`] = value + versionResults[`${id}_v${version}`][`f${ix}_chosen`] = chosen + versionResults[`${id}_v${version}`][`f${ix}_total`] = total + versionResults[`${id}_v${version}`][`f${ix}_percentageChosen`] = percentageChosen + }) + } + }) + }) + + setCsvData(Object.values(versionResults)) + } else if (error) { + addToast(error.message, { appearance: 'error' }) + } + }, [loading, data, error]) + + const onGetQuestionStatistics = async (): Promise => { + setCsvData(null) + try { + await getQuestionStatistics({ + variables: { ids: itemsChecked }, + }) + } catch (e) { + console.error(e.message) + } + } + + const itemCount = itemsChecked.length + return (
@@ -66,65 +136,73 @@ function ActionBar({
{creationMode ? ( <> - - ) : ( <> + + + + handleDeleteQuestions(false)} onConfirm={(): void => handleDeleteQuestions(true)} @@ -134,15 +212,18 @@ function ActionBar({
- +
+ {csvData && } +