Skip to content

Commit

Permalink
fix(analytics): improve error-handling (#1443)
Browse files Browse the repository at this point in the history
* fix(analytics): greatly improve analytics error-handling

* test(lint): make linter happy

* fix(analytics): search

* fix(analytics): remove error message

* fix(client): fixed language loading

* fix(analytics): lumi file scanning case insensitive

* test(analytics): changed test to check case sensitivity

* fix(analytics): completed localization

* feat(analytics): added error locales

* refactor(analytics): added new strings to locales

* fix(analytics): fixed plurals

Co-authored-by: Sebastian Rettig <serettig@posteo.de>
  • Loading branch information
JPSchellenberg and sr258 committed Apr 15, 2021
1 parent 653410e commit 287c675
Show file tree
Hide file tree
Showing 51 changed files with 6,314 additions and 200 deletions.
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": "{{count}} gebreekte lêer gevind",
"brokenFiles_plural": "{{count}} 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": "{{count}} የተሰበረ ፋይል ተገኝቷል",
"brokenFiles_plural": "{{count}} የተሰበሩ ፋይሎች ተገኝተዋል"
}
},
"h5peditor": {
Expand Down
Loading

0 comments on commit 287c675

Please sign in to comment.