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

Custom text and binary attachments in test apps #179

Merged
merged 13 commits into from
Dec 11, 2017
69 changes: 69 additions & 0 deletions TestApp/AttachmentsProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { AsyncStorage } from 'react-native';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

styling: use 2 space indentation instead of 4 space

import RNFS from 'react-native-fs';

const TEXT_ATTACHMENT_KEY = 'TEXT_ATTACHMENT_KEY';
const BINARY_FILENAME_KEY = 'BINARY_FILENAME_KEY';
const BINARY_FILETYPE_KEY = 'BINARY_FILETYPE_KEY';
const BINARY_FILESIZE_KEY = 'BINARY_FILESIZE_KEY';

const DEFAULT_FILENAME = 'binary.txt';
const DEFAULT_ENCODING = 'utf8';

export default class AttachmentsProvider {
static async saveTextAttachment(value) {
await AsyncStorage.setItem(TEXT_ATTACHMENT_KEY, value);
}

static async getTextAttachment() {
return getItemFromStorage(TEXT_ATTACHMENT_KEY, 'hello');
}

static async saveBinaryAttachment(name, data, type, size) {
AsyncStorage.setItem(BINARY_FILENAME_KEY, name);
AsyncStorage.setItem(BINARY_FILETYPE_KEY, type);
AsyncStorage.setItem(BINARY_FILESIZE_KEY, size);
saveFileInDocumentsFolder(DEFAULT_FILENAME, data);
}

static async getBinaryAttachment() {
const path = `${RNFS.DocumentDirectoryPath}/${DEFAULT_FILENAME}`;
let contents = '';
try {
contents = await RNFS.readFile(path, DEFAULT_ENCODING);
} catch (error) {
console.error('Error while reading binary attachment file');
}
return contents;
}

static async getBinaryName() {
return getItemFromStorage(BINARY_FILENAME_KEY);
}

static async getBinaryType() {
return getItemFromStorage(BINARY_FILETYPE_KEY);
}

static async getBinaryAttachmentInfo() {
const fileName = await getItemFromStorage(BINARY_FILENAME_KEY);
const fileSize = await getItemFromStorage(BINARY_FILESIZE_KEY);
return `${fileName} (${fileSize})`;
}
}

async function getItemFromStorage(key, defaultValue = '') {
try {
return await AsyncStorage.getItem(key);
} catch (error) {
console.error(`Error retrieving item with key: ${key}`);
console.error(error.message);
}
return defaultValue;
}

async function saveFileInDocumentsFolder(fileName, data) {
const path = `${RNFS.DocumentDirectoryPath}/${fileName}`;
RNFS.writeFile(path, data, DEFAULT_ENCODING)
.then(() => console.log('Binary attachment saved'))
.catch(err => console.error(err.message));
}
106 changes: 103 additions & 3 deletions TestApp/CrashesScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,32 @@ import {
StyleSheet,
Text,
View,
TextInput,
ScrollView,
TouchableOpacity
} from 'react-native';

import { DialogComponent } from 'react-native-dialog-component';
import ImagePicker from 'react-native-image-picker';

import Crashes from 'appcenter-crashes';
import { FooClass } from './js/FooClass';
import SharedStyles from './SharedStyles';
import AttachmentsProvider from './AttachmentsProvider';

export default class CrashesScreen extends Component {
constructor() {
super();
this.state = {
crashesEnabled: false,
lastSessionStatus: '',
sendStatus: ''
textAttachment: '',
binaryAttachment: ''
};
this.toggleEnabled = this.toggleEnabled.bind(this);
this.jsCrash = this.jsCrash.bind(this);
this.nativeCrash = this.nativeCrash.bind(this);
this.showFilePicker = this.showFilePicker.bind(this);
}

async componentDidMount() {
Expand All @@ -48,6 +55,12 @@ export default class CrashesScreen extends Component {
status += JSON.stringify(crashReport, null, 4);
component.setState({ lastSessionStatus: status });
}

const textAttachmentValue = await AttachmentsProvider.getTextAttachment();
component.setState({ textAttachment: textAttachmentValue });

const binaryAttachmentValue = await AttachmentsProvider.getBinaryAttachmentInfo();
component.setState({ binaryAttachment: binaryAttachmentValue });
}

async toggleEnabled() {
Expand All @@ -73,7 +86,6 @@ export default class CrashesScreen extends Component {
<Text style={SharedStyles.heading}>
Test Crashes
</Text>

<Text style={SharedStyles.enabledText}>
Crashes enabled: {this.state.crashesEnabled ? 'yes' : 'no'}
</Text>
Expand All @@ -82,7 +94,6 @@ export default class CrashesScreen extends Component {
toggle
</Text>
</TouchableOpacity>

<TouchableOpacity onPress={this.jsCrash}>
<Text style={styles.button}>
Crash JavaScript
Expand All @@ -93,14 +104,103 @@ export default class CrashesScreen extends Component {
Crash native code
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => { this.dialogComponent.show(); }}>
<Text style={styles.button}>
Set text error attachment
</Text>
</TouchableOpacity>
<Text style={SharedStyles.enabledText}>{'Current value:'}{this.state.textAttachment}</Text>
<TouchableOpacity onPress={this.showFilePicker}>
<Text style={styles.button}>
Select image as binary error attachment
</Text>
</TouchableOpacity>
<Text style={SharedStyles.enabledText}>{'Current value:'}{this.state.binaryAttachment}</Text>
<Text style={styles.lastSessionHeader}>Last session:</Text>
<Text style={styles.lastSessionInfo}>
{this.state.lastSessionStatus}
</Text>
</ScrollView>
{this.getTextAttachmentDialog()}
</View>
);
}

getTextAttachmentDialog() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend check-in two large image files as part of the TestApp to make testing easier. Due to the error attachment size limit of 1.4 MB on android and 10 MB on ios, I recommend a image < 1.4 MB and another image < 10 MB.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or create two base64 strings instead of check-in binary image files. We can't assume testing devices have such large binary files for testing.

return (
<DialogComponent
ref={(dialogComponent) => { this.dialogComponent = dialogComponent; }}
width={0.9}
>
<View>
<TextInput
style={{
Copy link
Member

@dhei dhei Dec 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor all UI styles in this class into SharedStyles.js so that we keep all StyleSheet in one place.

height: 40, borderColor: 'gray', borderWidth: 1, margin: 8
}}
onChangeText={text => this.setState({ textAttachment: text })}
/>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}>
<TouchableOpacity
style={{ height: 50 }}
onPress={() => {
AttachmentsProvider.saveTextAttachment(this.state.textAttachment);
this.dialogComponent.dismiss();
}}
>
Copy link
Member

@dhei dhei Dec 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[styling]: please use html tag </TouchableOpacity> instead of > to be consistent with other JSX files in this repo.

<Text style={styles.button}>
Save
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{ height: 50 }}
onPress={() => { this.dialogComponent.dismiss(); }}
>
<Text style={styles.button}>
Cancel
</Text>
</TouchableOpacity>
</View>
</View>
</DialogComponent>
);
}

showFilePicker() {
ImagePicker.showImagePicker(null, async (response) => {
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else {
AttachmentsProvider.saveBinaryAttachment(getFileName(response), response.data, getFileType(response), getFileSize(response));
const binaryAttachmentValue = await AttachmentsProvider.getBinaryAttachmentInfo();
this.setState({ binaryAttachment: binaryAttachmentValue });
}
});

function getFileName(response) {
return response.fileName != null ? response.fileName : 'binary.jpeg';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use !== instead of !=.

fileName != null will false when fileName is null or undefined.

}

function getFileType(response) {
return response.type != null ? response.type : 'image/jpeg';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, see my other comment.

}

function getFileSize(response) {
const thresh = 1024;
const units = ['KiB', 'MiB', 'GiB'];
let fileSize = response.fileSize;
if (Math.abs(fileSize) < thresh) {
return `${fileSize} B`;
}
let u = -1;
do {
fileSize /= thresh;
++u;
} while (Math.abs(fileSize) >= thresh && u < units.length - 1);
return `${fileSize.toFixed(1)} ${units[u]}`;
}
}
}


Expand Down
34 changes: 12 additions & 22 deletions TestApp/MainScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import AppCenter from 'appcenter';
import Crashes, { UserConfirmation, ErrorAttachmentLog } from 'appcenter-crashes';
import Push from 'appcenter-push';
import SharedStyles from './SharedStyles';
import AttachmentsProvider from './AttachmentsProvider';

export default class MainScreen extends Component {
constructor() {
Expand All @@ -25,7 +26,6 @@ export default class MainScreen extends Component {

render() {
const { navigate } = this.props.navigation;

return (
<View style={SharedStyles.container}>
<Text style={SharedStyles.heading}>
Expand All @@ -42,8 +42,7 @@ export default class MainScreen extends Component {

Push.setListener({
onPushNotificationReceived(pushNotification) {
let message = pushNotification.message;
let title = pushNotification.title;
let [message, title] = pushNotification;

if (message === null || message === undefined) {
// Android messages received in the background don't include a message. On Android, that fact can be used to
Expand Down Expand Up @@ -97,10 +96,16 @@ Crashes.setListener({

getErrorAttachments(report) {
console.log(`Get error attachments for report with id: ${report.id}'`);
return [
ErrorAttachmentLog.attachmentWithText('hello', 'hello.txt'),
ErrorAttachmentLog.attachmentWithBinary(testIcon, 'icon.png', 'image/png')
];
return (async () => {
const [textAttachment, binaryAttachment, binaryName, binaryType] = await Promise.all([
AttachmentsProvider.getTextAttachment(),
AttachmentsProvider.getBinaryAttachment(),
AttachmentsProvider.getBinaryName(),
AttachmentsProvider.getBinaryType(),
]);
return [ErrorAttachmentLog.attachmentWithText(textAttachment, 'hello.txt'),
ErrorAttachmentLog.attachmentWithBinary(binaryAttachment, binaryName, binaryType)];
})();
},

onBeforeSending() {
Expand All @@ -115,18 +120,3 @@ Crashes.setListener({
console.log('Failed sending crash. onSendingFailed is invoked.');
}
});

const testIcon = `iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGP
C/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3Cc
ulE8AAAA1VBMVEXLLmPLLWPLLWLMMWXLLGLMMmbcdJftt8nYY4vKLGHSSXfp
qL799fj////oobnVVYDUUX3LL2TccpX12OL88fXrsMT56O7NNWjhhaT56O3S
SHfTT3z56e777vPcc5bQQXH22+Tuvc7sssX++vv66/DuvM3sssbYZIv22uT7
7vLvvs79+PrUUH3OOmzjjqr66u/99vj23OXZZo3YYIn89Pf++fv22uPYYorX
YIjZaI767PHuusz99/nbb5TPQHDqqsD55+3ggqL55ez11+HRSHfUUn7TT3vg
lpRpAAAAAWJLR0QN9rRh9QAAAJpJREFUGNNjYMAKGJmYmZD5LKxs7BxMDJws
UD4nFzcPLx8LA7+AIJjPKiQsIirGJy4hKSwFUsMpLSMrJ6+gqKTMqyLACRRg
klflUVPX4NXU0lbRAQkwMOnqiegbGBoZmyAJaJqamVtABYBaDNgtDXmtrG0g
AkBDNW3tFFRFTaGGgqyVtXfgE3d0cnZhQXYYk6ubIA6nY3oOGQAAubQPeKPu
sH8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDctMjhUMDM6NDE6MTUrMDI6
MDAk+3aMAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA3LTI4VDAzOjQxOjE1
KzAyOjAwVabOMAAAAABJRU5ErkJggg==`;
2 changes: 2 additions & 0 deletions TestApp/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ android {
}

dependencies {
compile project(':react-native-fs')
compile project(':react-native-image-picker')
compile project(':appcenter-push')
compile project(':appcenter-crashes')
compile project(':appcenter-analytics')
Expand Down
3 changes: 2 additions & 1 deletion TestApp/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
import android.util.Log;

import com.facebook.react.ReactApplication;
import com.microsoft.appcenter.reactnative.push.AppCenterReactNativePushPackage;
import com.microsoft.appcenter.reactnative.crashes.AppCenterReactNativeCrashesPackage;
import com.microsoft.appcenter.reactnative.analytics.AppCenterReactNativeAnalyticsPackage;
import com.microsoft.appcenter.reactnative.appcenter.AppCenterReactNativePackage;
import com.microsoft.appcenter.AppCenter;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.imagepicker.ImagePickerPackage;
import com.microsoft.appcenter.reactnative.push.AppCenterReactNativePushPackage;
import com.microsoft.appcenter.reactnative.crashes.AppCenterReactNativeCrashesPackage;
import com.microsoft.appcenter.reactnative.analytics.AppCenterReactNativeAnalyticsPackage;
import com.microsoft.appcenter.reactnative.appcenter.AppCenterReactNativePackage;
import com.microsoft.appcenter.AppCenter;
import com.rnfs.RNFSPackage;

import java.util.Arrays;
import java.util.List;
Expand All @@ -30,6 +32,8 @@ public boolean getUseDeveloperSupport() {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNFSPackage(),
new ImagePickerPackage(),
new AppCenterReactNativePushPackage(MainApplication.this),
new AppCenterReactNativePackage(MainApplication.this),
new AppCenterReactNativeCrashesPackage(MainApplication.this, getResources().getString(R.string.appCenterCrashes_whenToSendCrashes)),
Expand Down
4 changes: 4 additions & 0 deletions TestApp/android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
rootProject.name = 'TestApp'
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
include ':react-native-image-picker'
project(':react-native-image-picker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-picker/android')
include ':appcenter-push'
project(':appcenter-push').projectDir = new File(rootProject.projectDir, '../node_modules/appcenter-push/android')
include ':appcenter-crashes'
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading