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

fix(analytics): improve error-handling #1443

Merged
merged 12 commits into from
Apr 15, 2021
8 changes: 4 additions & 4 deletions client/src/state/Analytics/AnalyticsAPI.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import superagent from 'superagent';

import { IFile } from './AnalyticsTypes';

export async function importAnalytics(): Promise<{
users: any[];
interactions: any[];
files: IFile[];
}> {
const response = await superagent.get('/api/v1/analytics');

return {
users: response.body.users,
interactions: response.body.interactions
files: response.body
};
}
28 changes: 8 additions & 20 deletions client/src/state/Analytics/AnalyticsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,23 @@ export function importAnalytics(): any {

dispatch(track('Analytics', 'import'));
try {
const { users, interactions } = await API.importAnalytics();
const { files } = await API.importAnalytics();
dispatch(
track(
'Analytics',
'import',
`content-types`,
interactions.length
)
track('Analytics', 'import', `content-types`, files.length)
);

dispatch({
payload: { users, interactions },
payload: { files },
type: ANALYTICS_IMPORT_SUCCESS
});
} catch (error) {
Sentry.captureException(error);
try {
dispatch({
payload: { message: error.response.body.message },
type: ANALYTICS_IMPORT_ERROR
});
} catch (error) {
Sentry.captureException(error);

dispatch({
payload: { message: 'no valid data' },
type: ANALYTICS_IMPORT_ERROR
});
}
console.log(error);
dispatch({
payload: { message: JSON.stringify(error) },
type: ANALYTICS_IMPORT_ERROR
});
}
};
}
7 changes: 3 additions & 4 deletions client/src/state/Analytics/AnalyticsReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import {
} from './AnalyticsTypes';

export const initialState: IAnalyticsState = {
users: [],
interactions: []
files: []
};

const log = new Logger('reducer:analytics');
Expand All @@ -23,8 +22,8 @@ export default function analyticsReducer(
switch (action.type) {
case ANALYTICS_IMPORT_SUCCESS:
return {
users: action.payload.users,
interactions: action.payload.interactions
...state,
files: action.payload.files
};

default:
Expand Down
14 changes: 8 additions & 6 deletions client/src/state/Analytics/AnalyticsTypes.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { IInteraction } from '@lumieducation/xapi-aggregator';

interface IUser {
id: string;
export interface IFile {
file: string;
name: string;
contentHash: string;
interactions: IInteraction[];
results: number[];
error?: boolean;
code?: string;
}
// state

export interface IAnalyticsState {
users: IUser[];
interactions: IInteraction[];
files: IFile[];
}

export interface IState {
Expand All @@ -27,8 +30,7 @@ export interface IAnalyticsImportRequestAction {

export interface IAnalyticsImportSuccessAction {
payload: {
users: IUser[];
interactions: IInteraction[];
files: IFile[];
};
type: typeof ANALYTICS_IMPORT_SUCCESS;
}
Expand Down
112 changes: 95 additions & 17 deletions client/src/views/Analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,130 @@
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { useTranslation } from 'react-i18next';

import AnalyticsStartPage from './components/AnalyticsStartPage';
import AnalyticsToolbar from './components/AnalyticsToolbar';
import Paper from '@material-ui/core/Paper';
import ListSubheader from '@material-ui/core/ListSubheader';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import ListItemText from '@material-ui/core/ListItemText';

import CloseIcon from '@material-ui/icons/Close';

import { groupBy } from 'lodash';

import LumixAPIViewer from './components/AnalyticsTable';

import { actions, IState } from '../state';

export default function Analytics() {
const dispatch = useDispatch();
const users = useSelector((state: IState) => state.analytics.users);
const interactions = useSelector(
(state: IState) => state.analytics.interactions
);
const classes = useStyles();
const { t } = useTranslation();

const files = useSelector((state: IState) => state.analytics.files);
const [searchText, setSearchText] = useState('');

const f = files.filter((file) => !file.error);
const d = groupBy(f, (o) => o.contentHash);

let e = [];
for (const key in d) {
e.push(
<Paper className={classes.paper}>
<LumixAPIViewer
key={key}
interactions={d[key][0].interactions}
users={d[key]
.filter(
(v) =>
v.name
.toLowerCase()
.indexOf(searchText.toLowerCase()) > -1
)
.map((v) => {
return {
id: v.file,
name: v.name,
results: v.results,
error: v.error
};
})}
/>
</Paper>
);
}

const brokenFiles = files.filter((file) => file.error);

return (
<div style={{ marginTop: '64px' }}>
{users.length === 0 ? (
{files.length === 0 ? (
<AnalyticsStartPage
primaryButtonClick={() =>
dispatch(actions.analytics.importAnalytics())
}
/>
) : null}

{users.length > 0 ? (
{files.length > 0 ? (
<div>
<AnalyticsToolbar
openFolder={() =>
dispatch(actions.analytics.importAnalytics())
}
search={(text: string) => setSearchText(text)}
/>

<LumixAPIViewer
interactions={interactions}
users={users.filter(
(user) =>
user.name
.toLocaleLowerCase()
.indexOf(searchText.toLocaleLowerCase()) >
-1
)}
/>
{e}
</div>
) : null}
{brokenFiles.length > 0 ? (
<Paper className={classes.paper}>
<List
component="nav"
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader
component="div"
id="nested-list-subheader"
>
{t('analytics.brokenFiles')}
</ListSubheader>
}
>
{brokenFiles.map((brokenFile) => (
<ListItem key={brokenFile.file}>
<ListItemAvatar>
<Avatar className={classes.icon}>
<CloseIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={brokenFile.file}
secondary={t(
`analytics.errors.${brokenFile.code}`
)}
/>
</ListItem>
))}
</List>
</Paper>
) : null}
</div>
);
}

const useStyles = makeStyles((theme: Theme) => {
return {
paper: {
margin: '20px'
},
icon: {
background: theme.palette.error.main
}
};
});
8 changes: 5 additions & 3 deletions client/src/views/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ export default function AppContainer() {

useEffect(() => {
dispatch(actions.settings.getSettings()).then(
async ({ language }: { language: string }) => {
await i18n.loadLanguages(language);
i18n.changeLanguage(language);
async (settings: { language: string }) => {
if (settings?.language) {
await i18n.loadLanguages(settings.language);
i18n.changeLanguage(settings.language);
}
}
);
}, [dispatch, i18n]);
Expand Down
4 changes: 3 additions & 1 deletion client/src/views/components/AnalyticsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ const LumixAPIViewer = (props: {
<TableRow>
<TableCell>{t('analytics.name')}</TableCell>
{interactions.map((interaction) => (
<TableCell>{interaction.name}</TableCell>
<TableCell>
{interaction.title || interaction.name}
</TableCell>
))}
<TableCell></TableCell>
<TableCell>{t('analytics.average')}</TableCell>
Expand Down
7 changes: 4 additions & 3 deletions client/src/views/components/AnalyticsToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
fade,
makeStyles,
Expand Down Expand Up @@ -86,6 +86,7 @@ export default function AnalyticsToolbar(props: {
search: (text: string) => void;
}) {
const classes = useStyles();
const { t } = useTranslation();

return (
<div className={classes.grow}>
Expand All @@ -101,14 +102,14 @@ export default function AnalyticsToolbar(props: {
<OpenIcon />
</IconButton>
<Typography className={classes.title} variant="h6" noWrap>
Analytics
{t('analytics.startPage.title')}
</Typography>
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
placeholder="Search student names…"
placeholder={t('analytics.searchPlaceholder')}
classes={{
root: classes.inputRoot,
input: classes.inputInput
Expand Down
15 changes: 13 additions & 2 deletions locales/lumi/af.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,16 @@
"open": "Kies die lêergids met .lumi-lêers"
},
"average": "Gemiddeld",
"name": "Naam"
"name": "Naam",
"searchPlaceholder": "Soek studentename ...",
"brokenFiles": "Lys van gebreekte lêers",
"errors": {
"no-content-json": "Die H5P-inhoud is nie behoorlik by die .lumi-lêer gevoeg nie.",
"json-parse-error": "Die .lumi-lêer bevat ongeldige inhoud (JSON het nie ontleed nie).",
"determine-name": "Kon nie die naam van die student bepaal nie.",
"invalid-interactions": "Sommige interaksies is verbreek.",
"invalid-statements": "Sommige van die opgetekende studente-aksies is gebreek."
}
},
"bug_report": {
"title": "Foutverslae",
Expand Down Expand Up @@ -174,7 +183,9 @@
"analytics": {
"import": {
"success": "Voer verslaglêers in",
"error": "Geen geldige lêer gevind nie"
"error": "Geen geldige lêer gevind nie",
"brokenFiles": "{{numberBrokenFiles}} gebreekte lêer gevind",
"brokenFiles_plural": "{{numberBrokenFiles}} stukkende lêers gevind"
}
}
},
Expand Down
15 changes: 13 additions & 2 deletions locales/lumi/am.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,16 @@
"open": ".Lumi ፋይሎችን የያዘ አቃፊ ይምረጡ"
},
"name": "ስም",
"average": "አማካይ"
"average": "አማካይ",
"searchPlaceholder": "የተማሪ ስሞችን ይፈልጉ ...",
"brokenFiles": "የተሰበሩ ፋይሎች ዝርዝር",
"errors": {
"no-content-json": "የኤች 5 ፒ ይዘት ለ .lumi ፋይል በትክክል አልተጨመረም።",
"json-parse-error": ".Lumi ፋይል ልክ ያልሆነ ይዘት ይ containsል (JSON አልተመረመረም)።",
"determine-name": "የተማሪውን ስም መወሰን አልተቻለም።",
"invalid-interactions": "አንዳንድ ግንኙነቶች ተሰብረዋል ፡፡",
"invalid-statements": "ከተመዘገቡት የተማሪ እርምጃዎች መካከል አንዳንዶቹ ተሰብረዋል ፡፡"
}
},
"bug_report": {
"title": "የሳንካ እና የስንክል ሪፖርቶች",
Expand Down Expand Up @@ -161,7 +170,9 @@
"analytics": {
"import": {
"error": "ትክክለኛ ፋይሎች አልተገኙም",
"success": "ከውጭ የመጡ የሪፖርት ፋይሎች"
"success": "ከውጭ የመጡ የሪፖርት ፋይሎች",
"brokenFiles": "{{numberBrokenFiles}} የተሰበረ ፋይል ተገኝቷል",
"brokenFiles_plural": "{{numberBrokenFiles}} የተሰበሩ ፋይሎች ተገኝተዋል"
}
},
"h5peditor": {
Expand Down
Loading