Commit 761b663d authored by Dustin Eckhardt's avatar Dustin Eckhardt
Browse files

Merge branch 'develop' into 'master'

v1.0.2

See merge request !2
parents f92b5a6c e8de2e3b
Pipeline #121013381 passed with stages
in 8 minutes and 2 seconds
......@@ -30,6 +30,7 @@
"document": true,
"FileReader": true,
"localStorage": true,
"sessionStorage": true,
"window": true,
"fetch": true,
"it": true,
......
......@@ -63,6 +63,7 @@ include:
- template: SAST.gitlab-ci.yml
sast:
stage: analyse
dependencies: []
lint:
stage: analyse
......
1.0.2 - 13.11.2019
------------------
- The current version number is now displayed in the menu
- Added some manual breadcrumbs for error reports
- Slightly improved the identification of UI elements that are mentioned in auto generated breadcrumbs of error reports
- Error reports now include a user id that gets randomly generated on each new session. This is used to distinguish errors from different user sessions
1.0.1 - 07.11.2019
------------------
- Fixed an issue where saving changes to an existing year failed when being connected to Google Drive
......
{
"name": "pixel-diary",
"version": "1.0.1",
"version": "1.0.2",
"private": true,
"dependencies": {
"@bugsnag/browser": "^6.4.1",
......
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Menu, MenuItem } from '@material-ui/core';
import { ArrowBackIos, ArrowForwardIos, Lock, MoreVert, Security } from '@material-ui/icons';
import React, { createElement, useState } from 'react';
import { version } from '../../../../package.json';
import privacyInfo from '../../../privacy';
import CryptoService from '../../../services/crypto-service';
import StorageHandler from '../../../storage/storage-handler';
......@@ -79,7 +80,7 @@ const AppMenu: React.FC<AppMenuProps> = (
return (
<div>
<IconButton color="inherit" onClick={event => setAnchor(event.currentTarget)}>
<IconButton id="app-menu" color="inherit" onClick={event => setAnchor(event.currentTarget)}>
<MoreVert />
</IconButton>
<Menu
......@@ -91,6 +92,7 @@ const AppMenu: React.FC<AppMenuProps> = (
>
<li className="year-selection">
<IconButton
id="app-menu-prev-year"
color="inherit"
disabled={disabled}
onClick={() => setDisplayYear(displayYear - 1)}
......@@ -99,6 +101,7 @@ const AppMenu: React.FC<AppMenuProps> = (
</IconButton>
<span>{displayYear}</span>
<IconButton
id="app-menu-next-year"
color="inherit"
disabled={displayYear >= new Date().getFullYear() || disabled}
onClick={() => setDisplayYear(displayYear + 1)}
......@@ -121,9 +124,12 @@ const AppMenu: React.FC<AppMenuProps> = (
<Security />
<span>Privacy</span>
</MenuItem>
<MenuItem onClick={() => window.open(repository.url, '_blank')}>
<MenuItem id="app-menu-privacy" onClick={() => window.open(repository.url, '_blank')}>
<div className="menu-icon"><img src={repository.logoSrc} alt="Tanuki" /></div>
<span>{`View on ${repository.name}`}</span>
<span>
{`Source on ${repository.name}`}
<span className="sup">{`version ${version}`}</span>
</span>
</MenuItem>
</Menu>
......
......@@ -48,7 +48,7 @@ const CloudMenu: React.FC<CloudMenuProps> = ({ saving, onDisconnect }) => {
</div>
<span>
{CloudsMeta[variant].name}
{!CloudsMeta[variant].configured && <span className="unavailable">not available</span>}
{!CloudsMeta[variant].configured && <span className="sup warn">not available</span>}
</span>
</MenuItem>
);
......
......@@ -3,13 +3,17 @@
padding-left: 8px;
}
span.unavailable {
span.sup {
font-size: 10px;
font-weight: bold;
color: red;
color: #9e9e9e;
display: block;
padding: 0;
margin-top: -8px;
&.warn {
color: red;
}
}
.menu-icon {
......
......@@ -60,7 +60,7 @@ const DayDetails: React.FC<DayDetailsProps> = ({ date, values, onClose }) => {
`;
return (
<Dialog className="day-details" open onClose={() => onClose()}>
<Dialog id="day-details" className="day-details" open onClose={() => onClose()}>
<DialogTitle>{date.toLocaleDateString(dateString.locale, dateString.options)}</DialogTitle>
<DialogContent>
<div className="mood-selection">
......@@ -77,6 +77,7 @@ const DayDetails: React.FC<DayDetailsProps> = ({ date, values, onClose }) => {
</FormControl>
</div>
<TextField
id="day-details-note"
name="note"
label="Note"
multiline
......@@ -90,8 +91,8 @@ const DayDetails: React.FC<DayDetailsProps> = ({ date, values, onClose }) => {
/>
</DialogContent>
<DialogActions>
<Button onClick={() => onClose(inputData)} color="primary">Apply</Button>
<Button onClick={() => onClose()} color="primary">Close</Button>
<Button id="day-details-apply" onClick={() => onClose(inputData)} color="primary">Apply</Button>
<Button id="day-details-close" onClick={() => onClose()} color="primary">Close</Button>
</DialogActions>
</Dialog>
);
......
......@@ -39,6 +39,7 @@ const Lockscreen: React.FC<LockscreenProps> = ({ checkCipher, onUnlock }) => {
<Lock className="icon" />
<div className="input-wrapper">
<TextField
id="lockscreen-input"
className="input"
type="password"
label="Password"
......
......@@ -35,6 +35,7 @@ const PasswordInput: React.FC<PasswordInputProps> = ({ onClose }) => {
</p>
<div className="password">
<TextField
id="password-input-field"
type={showPassword ? 'text' : 'password'}
label="Password"
value={password}
......@@ -58,13 +59,14 @@ const PasswordInput: React.FC<PasswordInputProps> = ({ onClose }) => {
</DialogContent>
<DialogActions>
<Button
id="password-input-apply"
color="primary"
disabled={!password || password.length < 8}
onClick={() => onClose(password)}
>
{'Enable'}
</Button>
<Button onClick={() => onClose()} color="primary">Close</Button>
<Button id="password-input-cancel" onClick={() => onClose()} color="primary">Close</Button>
</DialogActions>
</Dialog>
);
......
......@@ -3,6 +3,13 @@ import bugsnagReact from '@bugsnag/plugin-react';
import React from 'react';
import { version } from '../../package.json';
try {
// generates a random number to distinguish errors from different user sessions
if (!sessionStorage.getItem('random')) {
sessionStorage.setItem('random', Math.floor(Math.random() * 1000000).toString());
}
} catch (e) { /* errors are ignored */ }
export const bugsnagClient = bugsnag({
apiKey: process.env.REACT_APP_BUGSNAG || 'disabled',
appVersion: version,
......@@ -21,6 +28,12 @@ export const bugsnagClient = bugsnag({
beforeSend: report => {
// don't notify if no key has been configured
if (!process.env.REACT_APP_BUGSNAG) report.ignore();
try {
report.user = { // eslint-disable-line no-param-reassign
id: localStorage.getItem('tester') || sessionStorage.getItem('random') || '[UNDEFINED]',
};
} catch (e) { /* errors are ignored */ }
},
});
bugsnagClient.use(bugsnagReact, React);
......
......@@ -40,6 +40,7 @@ export default abstract class CryptoService {
}
this.passphrase = passwordHash;
bugsnagClient.leaveBreadcrumb('Crypto Service initialized');
}
return CryptoJS.AES.encrypt(this.checkValue, this.passphrase).toString();
......
import { isArray } from 'util';
import { bugsnagClient } from '../helper/bugsnag';
import CryptoService from '../services/crypto-service';
import { CloudAuthenticationError, CloudInitError, CloudRateLimitError, CloudTransferError, LocalStorageError } from '../types/errors';
import SupportedClouds from '../types/supported-clouds';
......@@ -48,6 +49,7 @@ export default abstract class StorageHandler {
*/
static async save(key: string, value: string): Promise<void> {
if (!this.initialized) this.init();
bugsnagClient.leaveBreadcrumb('Saving to storage', key);
// plaintext will be returned, if encryption hasn't been enabled
const cryptoValue = key === 'encryption' ? value : CryptoService.encrypt(value);
......@@ -75,6 +77,7 @@ export default abstract class StorageHandler {
*/
static async load(key: string): Promise<string | null> {
if (!this.initialized) this.init();
bugsnagClient.leaveBreadcrumb('Loading from storage', key);
try {
let value: string | null = localStorage.getItem(key);
......@@ -126,6 +129,7 @@ export default abstract class StorageHandler {
try {
this.cloud.init();
localStorage.setItem('storage', variant.toString());
bugsnagClient.leaveBreadcrumb('Cloud storage connected');
} catch (e) {
if (!(e instanceof CloudInitError)) throw e;
......@@ -141,6 +145,7 @@ export default abstract class StorageHandler {
static async transferToCloud(progress?: (value: number) => void): Promise<void> {
if (!this.cloud || !this.index.length) return;
if ((await this.cloud.list()).length > 0) throw new CloudTransferError();
bugsnagClient.leaveBreadcrumb('Transferring local data to cloud storage');
let done = 0;
const requests: Promise<void>[] = [];
......@@ -164,6 +169,7 @@ export default abstract class StorageHandler {
* first enabled, where all unencrypted data is loaded, and then encrypted when it is saved again
*/
static async rewriteAll(progress?: (value: number) => void): Promise<void> {
bugsnagClient.leaveBreadcrumb('Rewriting storage data');
const keys = await this.list();
const loadRequests: Promise<void>[] = [];
const saveRequests: Promise<void>[] = [];
......@@ -193,6 +199,7 @@ export default abstract class StorageHandler {
this.cloud.disconnect();
this.cloud = false;
localStorage.removeItem('storage');
bugsnagClient.leaveBreadcrumb('Cloud storage disconnected');
}
/**
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment