Skip to content

Commit

Permalink
feat(map): download collection metrics [EP-3002] (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-qc authored Nov 27, 2020
1 parent 99e1d4a commit 7815e82
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 24 deletions.
2 changes: 2 additions & 0 deletions packages/earth-map/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"@types/react-redux": "^7.1.7",
"chroma-js": "^2.1.0",
"core-js": "^3.6.5",
"file-saver": "^2.0.5",
"fuse.js": "^3.4.4",
"i18next": "^19.8.3",
"jszip": "^3.5.0",
"lodash": "^4.17.15",
"orbit-controls-es6": "^2.0.0",
"prettier": "^1.19.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import CollectionDelete from '../collection-delete';
import { CollectionEditPlaces } from '../collection-editplaces';
import { CollectionRename } from '../collection-rename';
import { CollectionDownloadMetrics } from '../collection-downloadmetrics';
import './styles.scss';

interface IProps {
Expand All @@ -54,6 +55,9 @@ const CollectionDetails = (props: IProps) => {
const [isAddingPlaces, setIsAddingPlaces] = useState(false);
const [isRenaming, setIsRenaming] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [isOnDownloadMetrics, setIsOnDownloadMetrics] = useState(false);
const [isDownloadingMetrics, setIsDownloadingMetrics] = useState(false);
const [downloadError, setDownloadError] = useState('');

const canEdit = privateGroups.includes(data.organization);

Expand Down Expand Up @@ -94,30 +98,66 @@ const CollectionDetails = (props: IProps) => {
</Card>

{hasLocations ? (
<Card className="c-legend-item-group">
{canEdit && (
<>
<Card className="c-legend-item-group">
{canEdit && (
<button
className="marapp-qa-actioneditinline ng-button ng-button-link ng-edit-card-button ng-text-transform-remove"
onClick={toggleEditPlaces}
>
{t('edit')}
</button>
)}
<h2 className="ng-text-display-s ng-body-color ng-margin-medium-bottom ng-margin-top-remove">
{t('Collection places')} ({locations.length})
</h2>
<p>
{locations
.filter((x) => !!x)
.map((location) => (
<Pill
label={location.name}
key={location.id}
className="marapp-qa-locationpill ng-margin-small-right ng-margin-small-bottom"
/>
))}
</p>
</Card>
<Card className="c-legend-item-group ng-margin-top">
<h2 className="ng-text-display-s ng-body-color ng-margin-medium-bottom ng-margin-top-remove">
{t('Download metrics')}
&nbsp;
<i className="ng-icon-download-outline ng-vertical-align-middle" />
</h2>
<p>
{isDownloadingMetrics ? (
<>{t('Your selected metric files should be ready soon')}.</>
) : (
<>
{t(
'Individual metrics related to each of the places in your collection can be viewed once downloaded'
)}
.{t('Select single or multiple metric data files for download')}.
</>
)}
</p>
<button
className="marapp-qa-actioneditinline ng-button ng-button-link ng-edit-card-button ng-text-transform-remove"
onClick={toggleEditPlaces}
className="marapp-qa-actiondownloadmetrics ng-button ng-button-secondary"
onClick={() => setIsOnDownloadMetrics(true)}
disabled={isDownloadingMetrics}
>
{t('edit')}
{isDownloadingMetrics ? (
<>
<Spinner size="nano" position="relative" className="ng-display-inline" />
{t('Downloading metrics')}
</>
) : (
<>{t('Download metric data files')}</>
)}
</button>
)}
<h2 className="ng-text-display-s ng-body-color ng-margin-medium-bottom ng-margin-top-remove">
{t('Collection places')} ({locations.length})
</h2>
<p>
{locations
.filter((x) => !!x)
.map((location) => (
<Pill
label={location.name}
key={location.id}
className="marapp-qa-locationpill ng-margin-small-right ng-margin-small-bottom"
/>
))}
</p>
</Card>
{downloadError && <p className="ng-form-error-block ng-margin-top">{downloadError}</p>}
</Card>
</>
) : (
<Card className="c-legend-item-group">
<h2 className="ng-text-display-s ng-body-color ng-margin-bottom">
Expand Down Expand Up @@ -158,6 +198,17 @@ const CollectionDetails = (props: IProps) => {
{isDeleting && (
<CollectionDelete collection={data} isDeleting={isDeleting} setIsDeleting={setIsDeleting} />
)}

{isOnDownloadMetrics && (
<CollectionDownloadMetrics
collection={data}
onCancel={() => setIsOnDownloadMetrics(false)}
onDownloadStart={() => [setIsDownloadingMetrics(true), setIsOnDownloadMetrics(false)]}
onDownloadEnd={() => setIsDownloadingMetrics(false)}
onDownloadError={setDownloadError}
onDownloadSuccess={() => setDownloadError('')}
/>
)}
</div>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* Copyright 2018-2020 National Geographic Society
*
* Use of this software does not constitute endorsement by National Geographic
* Society (NGS). The NGS name and NGS logo may not be used for any purpose without
* written permission from NGS.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

import { ICollection } from 'modules/collections/model';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Controller, useForm } from 'react-hook-form';

import { TitleHero, Card, ReactSelect, serializeFilters } from '@marapp/earth-shared';
import MetricService from 'services/MetricService';

import JSZip from 'jszip';
import FileSaver from 'file-saver';
import json2csv from 'json2csv';
import { groupBy } from 'lodash';
import flatten from 'flat';

interface IProps {
collection: ICollection;
onCancel: () => void;
onDownloadStart: () => void;
onDownloadEnd: () => void;
onDownloadError: (err: string) => void;
onDownloadSuccess: () => void;
}

export function CollectionDownloadMetrics(props: IProps) {
const {
collection,
onCancel,
onDownloadStart,
onDownloadEnd,
onDownloadError,
onDownloadSuccess,
} = props;
const { t } = useTranslation();
const { name, organization, slug: collectionSlug } = collection;
const [metricSlugs, setMetricSlugs] = useState([]);
const [isLoadingMetricSlugs, setIsLoadingMetricSlugs] = useState(false);
const { register, handleSubmit, formState, control, watch } = useForm({
mode: 'onChange',
});
const { dirty, isValid, isSubmitting } = formState;
const metricsWatcher = watch('metrics');

useEffect(() => {
(async () => {
setIsLoadingMetricSlugs(true);

const data = await MetricService.fetchMetricSlugs({ group: organization });

setMetricSlugs(data.map((item) => ({ value: item.slug, label: item.slug })));

setIsLoadingMetricSlugs(false);
})();
}, []);

return (
<form onSubmit={handleSubmit(onSubmit)} className="sidebar-content-full ng-form ng-form-dark">
<Card elevation="high" className="ng-margin-bottom">
<TitleHero title={name} subtitle={organization} extra={t('Collection')} />
</Card>

<div className="scroll-container">
<Card elevation="raised">
<label>{t('Select metrics for download')}</label>
<Controller
as={ReactSelect}
name="metrics"
type="metrics"
placeholder={t('Select metrics to download data files')}
className="marapp-qa-downloadmetricsdropdown ng-margin-medium-bottom"
options={metricSlugs}
isLoading={isLoadingMetricSlugs}
defaultValue={[]}
control={control}
isClearable={true}
isSearchable={true}
isMulti={true}
closeMenuOnSelect={false}
/>
<label>{t('Select a file type for download')}</label>
<div className="legend-item-group--radio ng-margin-top ng-margin-medium-bottom">
<div className="ng-display-inline-block ng-margin-medium-right">
<input
type="radio"
id={`radio-csv`}
value={'csv'}
name="fileType"
ref={register({
required: true,
})}
className="marapp-qa-downloadmetricscsv"
/>
<label htmlFor={`radio-csv`}>
<span className="legend-item-group--symbol" />
<span className="legend-item-group--name">CSV</span>
</label>
</div>
<div className="ng-display-inline-block ng-margin-medium-left">
<input
type="radio"
id={`radio-json`}
value={'json'}
name="fileType"
ref={register({
required: true,
})}
className="marapp-qa-downloadmetricsjson"
/>
<label htmlFor={`radio-json`}>
<span className="legend-item-group--symbol" />
<span className="legend-item-group--name">JSON</span>
</label>
</div>
</div>
<button
type="submit"
className="marapp-qa-actiondownload ng-button ng-button-primary ng-margin-right"
disabled={!isValid || isSubmitting || !dirty || !metricsWatcher?.length}
>
{t('Download')}
</button>
<button
className="marapp-qa-actioncancel ng-button ng-button-secondary"
onClick={onCancel}
>
{t('Cancel')}
</button>
</Card>
</div>
</form>
);

async function onSubmit(values) {
onDownloadStart();

const { metrics, fileType } = values;

try {
const data = await MetricService.downloadMetrics(collectionSlug, {
filter: serializeFilters({
slug: metrics.map((metric) => metric.value),
}),
group: organization,
include: 'location',
select: 'location.name',
});

const zip = new JSZip();

const metricTypes = groupBy(data, 'slug');
const locationNameField = '#';

Object.keys(metricTypes).forEach((metricType) => {
const normalizedData = metricTypes[metricType].map((item) => ({
[locationNameField]: item.location.name,
...item.metric,
}));
const fileName = `${metricType}.${fileType}`;

if (fileType === 'csv') {
const json2csvParser = new json2csv.Parser();

zip.file(
fileName,
json2csvParser.parse(
normalizedData.map(({ [locationNameField]: locationName, ...item }) => ({
[locationNameField]: locationName,
...flatten(item),
}))
)
);
} else {
zip.file(fileName, JSON.stringify(normalizedData));
}
});

const zipName = `${collectionSlug}-metrics.zip`;
const zipContent = await zip.generateAsync({ type: 'blob' });

FileSaver.saveAs(zipContent, zipName);
onDownloadSuccess();
} catch (e) {
onDownloadError('Something went wrong');
console.log(e);
}

onDownloadEnd();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2018-2020 National Geographic Society
*
* Use of this software does not constitute endorsement by National Geographic
* Society (NGS). The NGS name and NGS logo may not be used for any purpose without
* written permission from NGS.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

export { CollectionDownloadMetrics } from './CollectionDownloadMetrics';
10 changes: 10 additions & 0 deletions packages/earth-map/src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
"Renaming collection": "Renaming collection",
"Delete": "Delete",
"Are you sure you want to permanently delete this collection": "Are you sure you want to permanently delete this collection",
"Download metrics": "Download metrics",
"Downloading metrics": "Downloading metrics",
"Individual metrics related to each of the places in your collection can be viewed once downloaded": "Individual metrics related to each of the places in your collection can be viewed once downloaded",
"Select single or multiple metric data files for download": "Select single or multiple metric data files for download",
"Download metric data files": "Download metric data files",
"Your selected metric files should be ready soon": "Your selected metric files should be ready soon",
"Select metrics for download": "Select metrics for download",
"Select metrics to download data files": "Select metrics to download data files",
"Select a file type for download": "Select a file type for download",
"Download": "Download",
"Search results": "Search results",
"Download metric as a": "Download metric as a",
"Featured places": "Featured places",
Expand Down
10 changes: 10 additions & 0 deletions packages/earth-map/src/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
"Renaming collection": "Renombrar colección",
"Delete": "Eliminar",
"Are you sure you want to permanently delete this collection": "¿Estás seguro de que deseas eliminar esta colección de forma permanente",
"Download metrics": "Descargar métricas",
"Downloading metrics": "Descarga de métricas",
"Individual metrics related to each of the places in your collection can be viewed once downloaded": "Las métricas individuales relacionadas con cada uno de los lugares de su colección se pueden ver una vez descargadas",
"Select single or multiple metric data files for download": "Seleccione archivos de datos de métricas únicos o múltiples para descargar",
"Download metric data files": "Descargar archivos de datos de métricas",
"Your selected metric files should be ready soon": "Sus archivos de métricas seleccionados deberían estar listos pronto",
"Select metrics for download": "Seleccionar métricas para descargar",
"Select metrics to download data files": "Seleccionar métricas para descargar archivos de datos",
"Select a file type for download": "Seleccione un tipo de archivo para descargar",
"Download": "Descargar",
"Search results": "Resultados de la búsqueda",
"Download metric as a": "Descargar métrica como",
"Featured places": "Lugares destacados",
Expand Down
Loading

0 comments on commit 7815e82

Please sign in to comment.