Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW][ENTERPRISE] Option to download engagement data #17920

Merged
merged 3 commits into from Jun 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/components/basic/Buttons/ActionButton.js
@@ -0,0 +1,4 @@
import React from 'react';
import { Button, Icon } from '@rocket.chat/fuselage';
// TODO fuselage
export const ActionButton = ({ icon, ...props }) => <Button {...props} square ghost small><Icon name={icon} size='x20'/></Button>;
9 changes: 9 additions & 0 deletions client/lib/saveFile.js
@@ -0,0 +1,9 @@
export const saveFile = (content, name = 'download') => {
const blob = new Blob([content], { type: 'text/plain' });
const anchor = document.createElement('a');

anchor.download = name;
anchor.href = (window.webkitURL || window.URL).createObjectURL(blob);
anchor.dataset.downloadurl = ['text/plain', anchor.download, anchor.href].join(':');
anchor.click();
};
Expand Up @@ -6,6 +6,11 @@ import { useTranslation } from '../../../../../../client/contexts/TranslationCon
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import Growth from '../../../../../../client/components/data/Growth';
import { Section } from '../Section';
import { ActionButton } from '../../../../../../client/components/basic/Buttons/ActionButton';
import { saveFile } from '../../../../../../client/lib/saveFile';

const convertDataToCSV = (data) => `// type, name, messagesCount, updatedAt, createdAt
${ data.map(({ createdAt, messagesCount, name, t, updatedAt }) => `${ t }, ${ name }, ${ messagesCount }, ${ updatedAt }, ${ createdAt }`).join('\n') }`;

export function TableSection() {
const t = useTranslation();
Expand Down Expand Up @@ -73,7 +78,11 @@ export function TableSection() {
}));
}, [data]);

return <Section filter={<Select options={periodOptions} value={periodId} onChange={handlePeriodChange} />}>
const downloadData = () => {
saveFile(convertDataToCSV(channels), `Channels_start_${ params.start }_end_${ params.end }.csv`);
};

return <Section filter={<><Select options={periodOptions} value={periodId} onChange={handlePeriodChange} /><ActionButton mis='x16' disabled={!channels} onClick={downloadData} aria-label={t('Download_Info')} icon='download'/></>}>
<Box>
{channels && !channels.length && <Tile fontScale='p1' color='info' style={{ textAlign: 'center' }}>
{t('No_data_found')}
Expand Down
@@ -1,4 +1,4 @@
import { Box, Margins, Tabs } from '@rocket.chat/fuselage';
import { Box, Tabs } from '@rocket.chat/fuselage';
import React, { useMemo } from 'react';

import { useTranslation } from '../../../../../client/contexts/TranslationContext';
Expand All @@ -7,8 +7,6 @@ import { UsersTab } from './UsersTab';
import { MessagesTab } from './MessagesTab';
import { ChannelsTab } from './ChannelsTab';

const style = { padding: 0 };

export function EngagementDashboardPage({
tab = 'users',
onSelectTab,
Expand All @@ -24,14 +22,12 @@ export function EngagementDashboardPage({
<Tabs.Item selected={tab === 'messages'} onClick={handleTabClick('messages')}>{t('Messages')}</Tabs.Item>
<Tabs.Item selected={tab === 'channels'} onClick={handleTabClick('channels')}>{t('Channels')}</Tabs.Item>
</Tabs>
<Page.Content style={style}>
<Margins all='x24'>
<Box>
{(tab === 'users' && <UsersTab />)
|| (tab === 'messages' && <MessagesTab />)
|| (tab === 'channels' && <ChannelsTab />)}
</Box>
</Margins>
</Page.Content>
<Page.ScrollableContent padding={0}>
<Box m='x24'>
{(tab === 'users' && <UsersTab />)
|| (tab === 'messages' && <MessagesTab />)
|| (tab === 'channels' && <ChannelsTab />)}
</Box>
</Page.ScrollableContent>
</Page>;
}
Expand Up @@ -7,6 +7,11 @@ import { useTranslation } from '../../../../../../client/contexts/TranslationCon
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import { LegendSymbol } from '../data/LegendSymbol';
import { Section } from '../Section';
import { ActionButton } from '../../../../../../client/components/basic/Buttons/ActionButton';
import { saveFile } from '../../../../../../client/lib/saveFile';

const convertDataToCSV = (data) => `// type, messagesSent
${ data.map(({ t, messages }) => `${ t }, ${ messages }`).join('\n') }`;

export function MessagesPerChannelSection() {
const t = useTranslation();
Expand Down Expand Up @@ -64,9 +69,14 @@ export function MessagesPerChannelSection() {
return [pie, table];
}, [period, pieData, tableData]);

const downloadData = () => {
saveFile(convertDataToCSV(pieData.origins), `MessagesPerChannelSection_start_${ params.start }_end_${ params.end }.csv`);
};


return <Section
title={t('Where_are_the_messages_being_sent?')}
filter={<Select options={periodOptions} value={periodId} onChange={handlePeriodChange} />}
filter={<><Select options={periodOptions} value={periodId} onChange={handlePeriodChange} /><ActionButton mis='x16' disabled={!pieData} onClick={downloadData} aria-label={t('Download_Info')} icon='download'/></>}
>
<Flex.Container>
<Margins inline='neg-x12'>
Expand Down
Expand Up @@ -7,6 +7,11 @@ import { useTranslation } from '../../../../../../client/contexts/TranslationCon
import { useEndpointData } from '../../../../../../client/hooks/useEndpointData';
import CounterSet from '../../../../../../client/components/data/CounterSet';
import { Section } from '../Section';
import { ActionButton } from '../../../../../../client/components/basic/Buttons/ActionButton';
import { saveFile } from '../../../../../../client/lib/saveFile';

const convertDataToCSV = (data) => `// date, newMessages
${ data.map(({ date, newMessages }) => `${ date }, ${ newMessages }`).join('\n') }`;

export function MessagesSentSection() {
const t = useTranslation();
Expand Down Expand Up @@ -81,9 +86,13 @@ export function MessagesSentSection() {
];
}, [data, period]);

const downloadData = () => {
saveFile(convertDataToCSV(values), `MessagesSentSection_start_${ params.start }_end_${ params.end }.csv`);
};

return <Section
title={t('Messages_sent')}
filter={<Select options={periodOptions} value={periodId} onChange={handlePeriodChange} />}
filter={<><Select options={periodOptions} value={periodId} onChange={handlePeriodChange} /><ActionButton mis='x16' disabled={!data} onClick={downloadData} aria-label={t('Download_Info')} icon='download'/></>}
>
<CounterSet
counters={[
Expand Down
16 changes: 7 additions & 9 deletions ee/app/engagement-dashboard/client/components/Section.js
Expand Up @@ -8,16 +8,14 @@ export function Section({
}) {
return <Box>
<Margins block='x24'>
<Flex.Container alignItems='center' wrap='no-wrap'>
<Box>
<Flex.Item grow={1}>
<Box fontScale='s2' color='default'>{title}</Box>
</Flex.Item>
{filter && <Flex.Item grow={0}>
<Box display='flex' alignItems='center' wrap='no-wrap'>
<Box flexGrow={1} fontScale='s2' color='default'>{title}</Box>
{filter && <Flex.Item grow={0}>
<Margins mi='x24'>
{filter}
</Flex.Item>}
</Box>
</Flex.Container>
</Margins>
</Flex.Item>}
</Box>
{children}
</Margins>
</Box>;
Expand Down
Expand Up @@ -8,6 +8,11 @@ import { useEndpointData } from '../../../../../../client/hooks/useEndpointData'
import CounterSet from '../../../../../../client/components/data/CounterSet';
import { LegendSymbol } from '../data/LegendSymbol';
import { Section } from '../Section';
import { ActionButton } from '../../../../../../client/components/basic/Buttons/ActionButton';
import { saveFile } from '../../../../../../client/lib/saveFile';

const convertDataToCSV = ({ countDailyActiveUsers, diffDailyActiveUsers, countWeeklyActiveUsers, diffWeeklyActiveUsers, countMonthlyActiveUsers, diffMonthlyActiveUsers, dauValues, wauValues, mauValues }) => `// countDailyActiveUsers, diffDailyActiveUsers, countWeeklyActiveUsers, diffWeeklyActiveUsers, countMonthlyActiveUsers, diffMonthlyActiveUsers, dauValues, wauValues, mauValues
${ countDailyActiveUsers }, ${ diffDailyActiveUsers }, ${ countWeeklyActiveUsers }, ${ diffWeeklyActiveUsers }, ${ countMonthlyActiveUsers }, ${ diffMonthlyActiveUsers }, ${ dauValues }, ${ wauValues }, ${ mauValues }`;

export function ActiveUsersSection() {
const t = useTranslation();
Expand Down Expand Up @@ -91,7 +96,22 @@ export function ActiveUsersSection() {
];
}, [period, data]);

return <Section title={t('Active_users')} filter={null}>
const downloadData = () => {
saveFile(convertDataToCSV({
countDailyActiveUsers,
diffDailyActiveUsers,
countWeeklyActiveUsers,
diffWeeklyActiveUsers,
countMonthlyActiveUsers,
diffMonthlyActiveUsers,
dauValues,
wauValues,
mauValues,
}), `ActiveUsersSection_start_${ params.start }_end_${ params.end }.csv`);
};


return <Section title={t('Active_users')} filter={<ActionButton disabled={!data} onClick={downloadData} aria-label={t('Download_Info')} icon='download'/>}>
<CounterSet
counters={[
{
Expand Down
Expand Up @@ -35,92 +35,82 @@ function ContentForHours({ displacement, onPreviousDateClick, onNextDateClick })
}, [data]);

return <>
<Flex.Container alignItems='center' justifyContent='center'>
<Box>
<Button ghost square small onClick={onPreviousDateClick}>
<Chevron left size='20' style={{ verticalAlign: 'middle' }} />
</Button>
<Flex.Item basis='25%'>
<Margins inline='x8'>
<Box is='span' style={{ textAlign: 'center' }}>
{currentDate.format(displacement < 7 ? 'dddd' : 'L')}
</Box>
</Margins>
</Flex.Item>
<Button ghost square small disabled={displacement === 0} onClick={onNextDateClick}>
<Chevron right size='20' style={{ verticalAlign: 'middle' }} />
</Button>
<Box display='flex' alignItems='center' justifyContent='center'>
<Button ghost square small onClick={onPreviousDateClick}>
<Chevron left size='x20' style={{ verticalAlign: 'middle' }} />
</Button>
<Box mi='x8' flexBasis='25%' is='span' style={{ textAlign: 'center' }}>
{currentDate.format(displacement < 7 ? 'dddd' : 'L')}
</Box>
</Flex.Container>
<Flex.Container>
{data
? <Box style={{ height: 196 }}>
<Flex.Item align='stretch' grow={1} shrink={0}>
<Box style={{ position: 'relative' }}>
<Box style={{ position: 'absolute', width: '100%', height: '100%' }}>
<ResponsiveBar
data={values}
indexBy='hour'
keys={['users']}
groupMode='grouped'
padding={0.25}
margin={{
// TODO: Get it from theme
bottom: 20,
}}
colors={[
// TODO: Get it from theme
'#1d74f5',
]}
enableLabel={false}
enableGridY={false}
axisTop={null}
axisRight={null}
axisBottom={{
tickSize: 0,
// TODO: Get it from theme
tickPadding: 4,
tickRotation: 0,
tickValues: 'every 2 hours',
format: (hour) => moment().set({ hour, minute: 0, second: 0 }).format('LT'),
}}
axisLeft={null}
animate={true}
motionStiffness={90}
motionDamping={15}
theme={{
// TODO: Get it from theme
axis: {
ticks: {
text: {
fill: '#9EA2A8',
fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif',
fontSize: '10px',
fontStyle: 'normal',
fontWeight: '600',
letterSpacing: '0.2px',
lineHeight: '12px',
},
},
},
tooltip: {
container: {
backgroundColor: '#1F2329',
boxShadow: '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)',
borderRadius: 2,
},
<Button ghost square small disabled={displacement === 0} onClick={onNextDateClick}>
<Chevron right size='x20' style={{ verticalAlign: 'middle' }} />
</Button>
</Box>
{data
? <Box display='flex' height='196px'>
<Box align='stretch' flexGrow={1} flexShrink={0} position='relative'>
<Box position='absolute' width='100%' height='100%'>
<ResponsiveBar
data={values}
indexBy='hour'
keys={['users']}
groupMode='grouped'
padding={0.25}
margin={{
// TODO: Get it from theme
bottom: 20,
}}
colors={[
// TODO: Get it from theme
'#1d74f5',
]}
enableLabel={false}
enableGridY={false}
axisTop={null}
axisRight={null}
axisBottom={{
tickSize: 0,
// TODO: Get it from theme
tickPadding: 4,
tickRotation: 0,
tickValues: 'every 2 hours',
format: (hour) => moment().set({ hour, minute: 0, second: 0 }).format('LT'),
}}
axisLeft={null}
animate={true}
motionStiffness={90}
motionDamping={15}
theme={{
// TODO: Get it from theme
axis: {
ticks: {
text: {
fill: '#9EA2A8',
fontFamily: 'Inter, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Meiryo UI", Arial, sans-serif',
fontSize: '10px',
fontStyle: 'normal',
fontWeight: '600',
letterSpacing: '0.2px',
lineHeight: '12px',
},
}}
tooltip={({ value }) => <Box fontScale='p2' color='alternative'>
{t('Value_users', { value })}
</Box>}
/>
</Box>
</Box>
</Flex.Item>
},
},
tooltip: {
container: {
backgroundColor: '#1F2329',
boxShadow: '0px 0px 12px rgba(47, 52, 61, 0.12), 0px 0px 2px rgba(47, 52, 61, 0.08)',
borderRadius: 2,
},
},
}}
tooltip={({ value }) => <Box fontScale='p2' color='alternative'>
{t('Value_users', { value })}
</Box>}
/>
</Box>
</Box>
: <Skeleton variant='rect' height={196} />}
</Flex.Container>
</Box>
: <Skeleton variant='rect' height={196} />}
</>;
}

Expand All @@ -141,7 +131,7 @@ function ContentForDays({ displacement, onPreviousDateClick, onNextDateClick })
<Flex.Container alignItems='center' justifyContent='center'>
<Box>
<Button ghost square small onClick={onPreviousDateClick}>
<Chevron left size='20' style={{ verticalAlign: 'middle' }} />
<Chevron left size='x20' style={{ verticalAlign: 'middle' }} />
</Button>
<Flex.Item basis='50%'>
<Margins inline='x8'>
Expand All @@ -151,7 +141,7 @@ function ContentForDays({ displacement, onPreviousDateClick, onNextDateClick })
</Margins>
</Flex.Item>
<Button ghost square small disabled={displacement === 0} onClick={onNextDateClick}>
<Chevron right size='20' style={{ verticalAlign: 'middle' }} />
<Chevron right size='x20' style={{ verticalAlign: 'middle' }} />
</Button>
</Box>
</Flex.Container>
Expand Down