Initial commit 🎉

fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2
Co-authored-by: Sebastian McKenzie <sebmck@fb.com>
Co-authored-by: John Knox <jknox@fb.com>
Co-authored-by: Emil Sjölander <emilsj@fb.com>
Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
Daniel Büchele
2018-04-13 08:38:06 -07:00
committed by Daniel Buchele
commit fbbf8cf16b
659 changed files with 87130 additions and 0 deletions

BIN
docs/assets/initial.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

BIN
docs/assets/layout.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

BIN
docs/assets/logs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

BIN
docs/assets/network.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

46
docs/communitcating.md Normal file
View File

@@ -0,0 +1,46 @@
---
id: communicating
title: Device Communication
sidebar_label: Device Communication
---
To start communicating with a client your plugin must implement the init function. Once this function has been called the active client can also be accessed via `this.client`. This `id` of the plugin in JavaScript much match the native plugin `id` to allow for them to communicate.
```javascript
class extends SonarPlugin {
static title = "MyPlugin";
static id = 'MyPlugin';
init() {
// Setup subscriptions etc using this.client
}
}
```
There are three main ways your desktop plugin can communicate with connected devices.
## Remote method calls
With remote method calls your plugin can call a method on the attached client. This is useful for querying information from the client that your plugin wants to display. The first parameter is the name of the client API and the second is the specific method on that API. Optionally a JSON object can be passed as an argument to the client.
```javascript
this.client.call('methodName', DATA).then(res => {
// res contains client response
});
```
This function return as promise so that you can await a potential response from the client. If you are calling a method on the client but don't expect a response then you should instead opt for using the `send()` function.
```javascript
this.client.send('methodName', DATA);
```
## Subscriptions
A client is not only able to respond to method calls but also push data directly to the Sonar desktop app. With the subscribe API your plugin can subscribe to there pushes from the client. Pass the name of the method and the API it is part of as well as a callback function to start a subscription. Any time the client sends a push matching this method the callback will be called with any attached data as a javascript object.
```javascript
this.client.subscribe('methodName', data => {
// data contains any payload sent by the client
});
```

140
docs/create-plugin.md Normal file
View File

@@ -0,0 +1,140 @@
---
id: create-plugin
title: Mobile Setup
sidebar_label: Mobile Setup
---
## Implement SonarPlugin
Create a class implementing `SonarPlugin`.
### Android
```java
public class MySonarPlugin implements SonarPlugin {
private SonarConnection mConnection;
@Override
public String getId() {
return "MySonarPlugin";
}
@Override
public void onConnect(SonarConnection connection) throws Exception {
mConnection = connection;
}
@Override
public void onDisconnect() throws Exception {
mConnection = null;
}
}
```
### iOS
```objective-c
@interface MySonarPlugin : NSObject<SonarPlugin>
@end
@implementation MySonarPlugin
- (NSString*)identifier { return @"MySonarPlugin"; }
- (void)didConnect:(SonarConnection*)connection {}
- (void)didDisonnect {}
@end
```
### C++
```c++
class MySonarPlugin : public SonarPlugin {
public:
std::string identifier() const override { return "MySonarPlugin"; }
void didConnect(std::shared_ptr<SonarConnection> conn) override;
void didDisconnect() override;
};
```
## Using SonarConnection
Using the `SonarConnection` object you can register a receiver of a desktop method call and respond with data.
### Android
```java
connection.receive("getData", new SonarReceiver() {
@Override
public void onReceive(SonarObject params, SonarResponder responder) throws Exception {
responder.success(
new SonarObject.Builder()
.put("data", MyData.get())
.build());
}
});
```
### iOS
```objective-c
@interface MySonarPlugin : NSObject<SonarPlugin>
@end
@implementation MySonarPlugin
- (NSString*)identifier { return @"MySonarPlugin"; }
- (void)didConnect:(SonarConnection*)connection
{
[connection receive:@"getData" withBlock:^(NSDictionary *params, SonarResponder *responder) {
[responder success:@{
@"data":[MyData get],
}];
}];
}
- (void)didDisonnect {}
@end
```
### C++
```c++
void MySonarPlugin::didConnect(std::shared_ptr<SonarConnection> conn) {
conn->receive("getData", [](const folly::dynamic &params,
std::unique_ptr<SonarResponder> responder) {
dynamic response = folly::dynamic::object("data", getMyData());
responder->success(response);
});
}
```
## Push data to the desktop
You don't have to wait for the desktop to request data though, you can also push data directly to the desktop.
### Android
```java
connection.send("MyMessage",
new SonarObject.Builder()
.put("message", "Hello")
.build()
```
### iOS
```objective-c
[connection send:@"getData" withParams:@{@"message":@"hello"}];
```
### C++
```c++
void MySonarPlugin::didConnect(std::shared_ptr<SonarConnection> conn) {
dynamic message = folly::dynamic::object("message", "hello");
conn->send("getData", message);
}
```

View File

@@ -0,0 +1,86 @@
---
id: create-table-plugin
title: Create Table Plugin
sidebar_label: Create Table Plugin
---
A very common kind of Sonar plugin is a plugin which fetches some structured data from the device and presents it in a table.
To make building these kinds of plugins as easy as possible we have created an abstraction we call `createTablePlugin`. This is a function which manages the complexities of building a table plugin but still allows you to customize many things to suite your needs.
Below is a sample implementation of a desktop plugin based on `createTablePlugin`. It subscribes to updates from a client plugin with id `myplugin` sending rows to with the `newRow` method. A row can have any structure you want as long as it has a unique field `id` of type `string`.
See "[Create Plugin](create-plugin.md)" for how to create the native counterpart for your plugin.
```javascript
import {ManagedDataInspector, Panel, Text, createTablePlugin} from 'sonar';
type Id = string;
type Row = {
id: Id,
column1: string,
column2: string,
column3: string,
extras: Object,
};
function buildRow(row: Row) {
return {
columns: {
column1: {
value: <Text>{row.column1}</Text>,
filterValue: row.column1,
},
column2: {
value: <Text>{row.column2}</Text>,
filterValue: row.column2,
},
column3: {
value: <Text>{row.column3}</Text>,
filterValue: row.column3,
},
},
key: row.id,
copyText: JSON.stringify(row),
filterValue: `${row.column1} ${row.column2} ${row.column3}`,
};
}
function renderSidebar(row: Row) {
return (
<Panel floating={false} heading={'Extras'}>
<ManagedDataInspector data={JSON.parse(row.extras)} expandRoot={true} />
</Panel>
);
}
const columns = {
time: {
value: 'Column1',
},
module: {
value: 'Column2',
},
name: {
value: 'Column3',
},
};
const columnSizes = {
time: '15%',
module: '20%',
name: 'flex',
};
export default createTablePlugin({
title: 'My Plugin', // Title of plugin
id: 'myplugin', // ID of plugin
method: 'newRow', // Method which should be subscribed to to get new rows with share Row (from above),
icon: 'washing-machine',
columns,
columnSizes,
renderSidebar,
buildRow,
});
```

32
docs/error-handling.md Normal file
View File

@@ -0,0 +1,32 @@
---
id: error-handling
title: Error Handling
sidebar_label: Error Handling
---
Errors in Sonar plugins should be hidden from the user while providing actionable data to the plugin developer.
## Android
To gracefully handle errors in Sonar we provide the `ErrorReportingRunnable` class. This is a custom runnable which catches all exceptions, stopping them from crashing the application and reporting them to the plugin developer.
```java
new ErrorReportingRunnable(mConnection) {
@Override
public void runOrThrow() throws Exception {
mightThrowException();
}
}.run();
```
Executing this block of code will always finish without error but may transfer any silences error to the Sonar desktop app. During plugin development these java stack traces are surfaced in the chrome dev console. In production the errors are instead sent to and a task is assigned so that you can quickly deploy a fix.
Always use `ErrorReportingRunnable` for error handling instead of `try`/`catch` or even worse letting errors crash the app. With ErrorReportingRunnable you won't block anyone and you won't hide any stack traces.
## C++
To gracefully handle errors in Sonar we perform all transactions inside of a try block which catches all exceptions, stopping them from crashing the application and reporting them to the plugin developer. This includes your own customs implementations of `SonarPlugin::didConnect()` and `SonarConnection::send()` and `::receive()`!
That means you can safely throw exceptions in your plugin code. The exception messages will be sent to the Sonar desktop app. During plugin development the exception messages are surfaced in the Chrome dev console.
If your plugin performs asynchronous work in which exceptions are thrown, these exceptions will not be caught by the Sonar infrastructure. You should handle them appropriately.

82
docs/getting-started.md Normal file
View File

@@ -0,0 +1,82 @@
---
id: getting-started
title: Getting Started
sidebar_label: Getting Started
---
Sonar helps you debug Android and iOS apps running in an emulator/simulator or connected physical development devices. Sonar consists of two parts:
* The desktop app for macOS
* The native mobile SDKs for Android and iOS
To use Sonar, you need to add the mobile SDK to your app.
## Setup
### Desktop app
The desktop part of Sonar doesn't need any particular setup. Simply [download the latest build](https://www.facebook.com/sonar/public/mac) of our app and launch it. The desktop app is available for macOS and requires a working installation of the Android/iOS development tools on your system.
Once you start Sonar and launch an emulator/simulator or connect a device, you will already be able to see the device logs in Sonar. To see app specific data, you need to integrate our native SDKs with your app.
![Logs plugin](/docs/assets/initial.png)
### Setup your Android app
TODO: Install dependencies
TODO: Add dependencies to your `build.gradle` file.
Now you can initialize Sonar in your Application's `onCreate`-method like this:
```java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG && SonarUtils.isMainProcess(mApplicationContext)) {
final SonarClient client = AndroidSonarClient.getInstance(this);
client.addPlugin(new MySonarPlugin());
client.start();
}
}
}
```
### Setup your iOS app
To integrate with our iOS app, you can use [CocoaPods](https://cocoapods.org). Add the mobile Sonar SDK to your `Podfile`:
```ruby
platform :ios, '8.0'
use_frameworks!
target 'MyApp' do
pod 'Sonar', '~> 1.0'
end
```
and install the dependencies by running `pod install`. When you open the Xcode workspace file for your app, you now can import and initialize Sonar in your AppDelegate.
```objective-c
#import <Sonar/SonarClient.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if DEBUG
SonarClient *client = [SonarClient sharedClient];
[client addPlugin:[MySonarPlugin new]];
[client start];
#endif
...
}
@end
```
## Ready for takeoff
Finally you need to add plugins to your Sonar client. See [Network Plugin](network-plugin.md) and [Layout Inspector Plugin](layout-plugin.md) on how to add them.

58
docs/jssetup.md Normal file
View File

@@ -0,0 +1,58 @@
---
id: js-setup
title: JavaScript Setup
sidebar_label: JavaScript Setup
---
## Creating the plugin UI
To create the desktop part of your plugin, initiate a new JavaScript project using `yarn init` and make sure your package name starts with `sonar-plugin-` and a file called `index.js`, which is the entry point to your plugin. A sample `package.json`-file could look like this:
```
{
"name": "sonar-plugin-myplugin",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {}
}
```
In `index.js` you can now create your plugin. We expect this file to have a default export of type `SonarPlugin`. A hello-world-plugin could look like this:
```js
import {SonarPlugin} from 'sonar';
export default class extends SonarPlugin {
render() {
return 'hello world';
}
}
```
Learn more on how to use [Sonar's UI components](ui-components.md).
### Dynamically loading plugins
Once a plugin is created, Sonar can load it from it's folder. The path from where the plugins are loaded is specified in `~/.sonar/config.json`. Add the parent folder of your plugin to `pluginPaths` and start Sonar.
### npm dependencies
If you need any dependencies in your plugin, you can install them using `yarn add`. The Sonar UI components exported from `sonar`, as well as `react` and `react-dom` don't need to be installed as dependencies. Our plugin-loader makes these dependencies available to your plugin.
### ES6, babel-transforms and bundling
Our plugin-loader is capable of all ES6 goodness, flow annotations and JSX and applies the required babel-transforms without you having to care about this. Also you don't need to bundle your plugin, you can simply use ES6 imports and it will work out of the box.
## Working on the core
If you only want to work on a plugin, you don't need to run the development build of Sonar, but you can use the production release. However, if you want to contribute to Sonar's core, add additional UI components, or do anything outside the scope of a single plugins this is how you run the development version of Sonar.
Make sure you have a recent version of node.js and yarn installed on your system (node ≥ 8, yarn ≥ 1.5). Then run the following commands:
```
git clone https://github.com/facebook/Sonar.git
cd Sonar
yarn
yarn start
```

54
docs/layout-plugin.md Normal file
View File

@@ -0,0 +1,54 @@
---
id: layout-plugin
title: Layout Inspector
---
The Layout Inspector in Sonar is useful for a ton of different debugging scenarios. First of all you can inspect what views the hierarchy is made up of as well as what properties each view has. This is incredibly useful when debugging issues with your product.
The Layout tab supports [Litho](https://fblitho.com) and [ComponentKit](https://componentkit.org) components as well! We integrate with these frameworks to present components in the hierarchy just as if they were native views. We show you all the layout properties, props, and state of the components. The layout inspector is further extensible to support other UI frameworks.
If you hover over a view or component in Sonar we will highlight the corresponding item in your app! This is perfect for debugging the bounds of your views and making sure you have the correct visual padding.
![Layout plugin](/docs/assets/layout.png)
## Setup
To use the layout inspector plugin, you need to add the plugin to your Sonar client instance.
### Android
```java
import com.facebook.sonar.plugins.inspector.DescriptorMapping;
import com.facebook.sonar.plugins.inspector.InspectorSonarPlugin;
final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
client.addPlugin(new InspectorSonarPlugin(mApplicationContext, descriptorMapping));
```
### iOS
```objective-c
#import <SonarKitLayoutPlugin/SonarKitLayoutPlugin.h>
#import <SonarKitLayoutPlugin/SKDescriptorMapper.h>
SKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults];
[client addPlugin:[[SonarKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]]
```
## Quick edits
The Layout Inspector not only allows you to view the hierarchy and inspect each item's properties, but it also allows you to edit things such as layout attributes, background colors, props, and state. Most things actually! This allows you to quickly tweak paddings, margins, and colors until you are happy with them, all without re-compiling. This can save you many hours implementing a new design.
## Target mode
Enable target mode by clicking on the crosshairs icon. Now, you can touch any view on the device and Layout Inspector will jump to the correct position within your layout hierarchy.
### Blocking fullscreen views (Android only)
The issue is that if you have some view that occupies big part of the screen but draws nothing and its Z-position is higher than your main content, then selecting view/component through Layout Inspector doesn't work as you intended, as it will always hit that transparent view and you need to manually navigate to the view you need which is time-consuming and should not be necessary.
Add the following tag to your view to skip it from Sonar's view picker. The view will still be shown in the layout hierarchy, but it will not be selected while using the view picker.
```java
view.setTag("sonar_skip_view_traversal", true);
```

18
docs/logs-plugin.md Normal file
View File

@@ -0,0 +1,18 @@
---
id: logs-plugin
title: Logs
---
The Logs plugin shows device logs, without any additional setup. This is a device plugin, meaning that it is not tied to any specific app and there is no additional setup needed to see the logs.
![Logs plugin](/docs/assets/logs.png)
## Filtering
The search bar can be used to search for logs and filter for certain types. From the context menu on the table headers you can show more infos like timestamp, PID or TID. Click on a tag, PID or TID in the table to filter only for logs with the same value.
## Expression watcher
The expression watcher in the sidebar can be used to watch for certain logs to happen and count how often the occur. An expression can be a simple string, or a regular expression which is matched against the logs.
When the notify checkbox is enabled, Sonar will send notifications once the log is happening. This let's you know when the watcher triggered, even if Sonar is in the background.

32
docs/network-plugin.md Normal file
View File

@@ -0,0 +1,32 @@
---
id: network-plugin
title: Network
---
Use the Network inspector to inspect outgoing network traffic our apps. You can easily browse all requests being made and their responses. The plugin also supports gzipped responses.
![Netowrk plugin](/docs/assets/network.png)
## Setup
To use the network plugin, you need to add the plugin to your Sonar client instance.
### Android
```java
import com.facebook.sonar.plugins.network.NetworkSonarPlugin;
client.addPlugin(new NetworkSonarPlugin());
```
### iOS
```objective-c
#import <SonarKitNetworkPlugin/SonarKitNetworkPlugin.h>
[client addPlugin: [SonarKitNetworkPlugin new]]
```
## Usage
All request sent from the device will be listed in the plugin. Click on a request to see details like headers and body. You can filter the table for domain, method or status by clicking on the corresponding value in the table.

50
docs/send-data.md Normal file
View File

@@ -0,0 +1,50 @@
---
id: send-data
title: Sending Data to Plugins
sidebar_label: Send Data
---
It is often useful to get an instance of a sonar plugin to send data to it. Sonar makes this simple with built in support.
Plugins should be treated as singleton instances as there can only be one `SonarClient` and each `SonarClient` can only have one instance of a certain plugin. The Sonar API makes this simple by offering a way to get the current client and query it for plugins.
Plugins are identified by the string that their identifier method returns, in this example, "MySonarPlugin":
### Android
```java
final SonarClient client = AndroidSonarClient.getInstance(context);
// Client may be null if AndroidSonarClient.createInstance() was never called
// which is the case in production builds.
if (client != null) {
final MySonarPlugin plugin = client.getPlugin("MySonarPlugin");
plugin.sendData(myData);
}
```
### iOS
```objective-c
SonarClient *client = [SonarClient sharedClient];
MySonarPlugin *myPlugin = [client pluginWithIdentifier:@"MySonarPlugin"];
[myPlugin sendData:myData];
```
### C++
```c++
auto &client = SonarClient::instance();
// "MySonarPlugin is the return value of MySonarPlugin::identifier()
auto aPlugin = client.getPlugin("MySonarPlugin");
// aPlugin is a std::shared_ptr<SonarPlugin>. Downcast to expected type.
auto myPlugin = std::static_pointer_cast<MySonarPlugin>(aPlugin);
// Alternatively, use the templated version
myPlugin = client.getPlugin<MySonarPlugin>("MySonarPlugin");
myPlugin->sendData(myData);
```
Here, `sendData` is an example of a method that might be implemented by the sonar plugin.

11
docs/stetho.md Normal file
View File

@@ -0,0 +1,11 @@
---
id: stetho
title: Stetho Guidance
sidebar_label: Stetho Guidance
---
In 2015, we introduced [Stetho](http://facebook.github.io/stetho/), an Android debugging bridge built on top of Chrome dev tools. While it was a valuable tool to us and many members of the community, we felt that it limited us in what we could do with it. Stetho is Android-only and while Chrome dev tools gave us a nice foundation to build upon, they also limited us in what we could build. Stetho is an Android tool and Chrome dev tools is built for web developers. This means we can only provide a good experience for the intersection of those two development environments, which was very limiting. With Sonar being built as a standalone app, we can do more things, like handling adb connections and supporting iOS, which wouldn't easily achievable with stetho.
This is why we built Sonar. We wanted to create a platform that gives us all the flexibility we need to build more advanced features and support for iOS. One of Sonar's core concept is it's extensibility using [plugins](create-plugin.md). Plugins are written in react and we provide a set of ready-to-use UI components that allows developers to build great plugin UIs with a few lines of code.
While offering many new features, Sonar also already covers most of the features provided by Stetho. This includes network and layout inspection, and an advanced log viewer. We are committed to continuously improving Sonar with new features and plugins, however we are aware that not all stetho features are currently supported by Sonar. If you are using a particular feature of Stetho which isn't supported by Sonar, we are more than happy to hear about your use-case. Stetho will continue to work and we are not abandoning it so you can choose the tool that fits your needs best. We are confident that Sonar will work well for most use-cases and are more than happy to accept contributions from the open-source community adding new features.

View File

@@ -0,0 +1,75 @@
---
id: styling-components
title: Styling Components
sidebar_label: Styling Components
---
We use a styled-component based approach to styling our views. This means styles are defined in JavaScript and are written as CSS-stylesheets to the DOM. A component and it's styles are coupled. Styled components can extend another to inherit their styles.
## Basic tags
For basic building blocks (views, texts, ...) you can use the styled object.
```javascript
import {styled} from 'sonar';
const MyView = styled.view({
fontSize: 10,
color: colors.red
});
const MyText = styled.text({ ... });
const MyImage = styled.image({ ... });
const MyInput = styled.input({ ... });
```
But you can use any other HTML-tags like this:
```javascript
styled.customHTMLTag('canvas', { ... });
```
## Extending Sonar Components
It's very common for components to require customizing Sonar's components in some way. For example changing colors, alignment, or wrapping behavior. There is a `extends` method on all styled components which allows adding or overwriting existing style rules.
For these use cases when a styled component is only used within the context of a single component we encourage declaring it as a inner static instance. This makes it clear where the component is used and avoids polluting the global namespace.
```javascript
class MyComponent extends Component {
static Container = FlexRow.extends({
alignItems: 'center',
});
render() {
return <MyComponent.Container>...</MyComponent.Container>;
}
}
```
## CSS
The CSS-in-JS object passed to the styled components takes just any CSS rule, with the difference that it uses camel cased keys for the properties. Pixel-values can be numbers, any other values need to be strings.
Dynamic values also can be functions, receiving the react props as argument. (Make sure to add properties passed to a component to the `ignoredAttributes` array to not be written to the DOM as an attribute.)
```javascript
const MyView = styled.view(
{
fontSize: 10,
color: props => (props.disabled ? colors.red : colors.black),
},
{
ignoredAttributes: ['disabled'],
}
);
```
Pseudo-classes can be used like this:
```javascript
'&:hover': {color: colors.red}`
```
## Colors
The colors module contains all standard colors used by Sonar. All the available colors are defined in `src/ui/components/colors.js` with comments about suggested usage of them. And we strongly encourage to use them.

96
docs/testing.md Normal file
View File

@@ -0,0 +1,96 @@
---
id: testing
title: Testing
sidebar_label: Testing
---
Developer tools are only used if they work. We have built APIs to test plugins.
## Android
Start by creating your first test file in this directory `MySonarPluginTest.java`. In the test method body we create our plugin which we want to test as well as a `SonarConnectionMock`. In this contrived example we simply assert that our plugin's connected status is what we expect.
```java
@RunWith(WithTestDefaultsRunner.class)
public class MySonarPluginTest {
@Test
public void myTest() {
final MySonarPlugin plugin = new MySonarPlugin();
final SonarConnectionMock connection = new SonarConnectionMock();
plugin.onConnect(connection);
assertThat(plugin.connected(), equalTo(true));
}
}
```
There are two mock classes that are used to construct tests `SonarConnectionMock` and `SonarResponderMock`. Together these can be used to write very powerful tests to verify the end to end behavior of your plugin. For example we can test if for a given incoming message our plugin responds as we expect.
```java
@Test
public void myTest() {
final MySonarPlugin plugin = new MySonarPlugin();
final SonarConnectionMock connection = new SonarConnectionMock();
final SonarResponderMock responder = new SonarResponderMock();
plugin.onConnect(connection);
final SonarObject params = new SonarObject.Builder()
.put("phrase", "sonar")
.build();
connection.receivers.get("myMethod").onReceive(params, responder);
assertThat(responder.successes, hasItem(
new SonarObject.Builder()
.put("phrase", "ranos")
.build()));
}
```
## C++
Start by creating your first test file in this directory `MySonarPluginTests.cpp` and import the testing utilities from `//xplat/sonar-client:SonarTestLib`. These utilities mock out core pieces of the communication channel so that you can test your plugin in isolation.
```
#include <MySonarPlugin/MySonarPlugin.h>
#include <SonarTestLib/SonarConnectionMock.h>
#include <SonarTestLib/SonarResponderMock.h>
#include <folly/json.h>
#include <gtest/gtest.h>
namespace facebook {
namespace sonar {
namespace test {
TEST(MySonarPluginTests, testDummy) {
EXPECT_EQ(1 + 1, 2);
}
} // namespace test
} // namespace sonar
} // namespace facebook
```
Here is a simple test using these mock utilities to create a plugin, send some data, and assert that the result is as expected.
```
TEST(MySonarPluginTests, testDummy) {
std::vector<folly::dynamic> successfulResponses;
auto responder = std::make_unique<SonarResponderMock>(&successfulResponses);
auto conn = std::make_shared<SonarConnectionMock>();
MySonarPlugin plugin;
plugin.didConnect(conn);
folly::dynamic message = folly::dynamic::object("param1", "hello");
folly::dynamic expectedResponse = folly::dynamic::object("response", "Hi there");
auto receiver = conn->receivers_["someMethod"];
receiver(message, std::move(responder));
EXPECT_EQ(successfulResponses.size(), 1);
EXPECT_EQ(successfulResponses.back(), expectedResponse);
}
```

254
docs/ui-components.md Normal file
View File

@@ -0,0 +1,254 @@
---
id: ui-components
title: UI Components
sidebar_label: UI Components
---
Sonar has a lot of built in React components to build UIs. You can find all of these in [`src/ui/components`](https://github.com/facebook/Sonar/tree/master/src/ui/components) and can import them directly using `import {Buttom} from 'sonar'`.
## FlexBox
In Sonar we make heavy use of flexbox for layout. FlexBox is layout system on the web which has been specifically design for building application like UIs. Sonar provides two flexbox components `FlexRow` and `FlexColumn`. These are flexbox components with some sane defaults such as automatically scrolling content that overflows.
```javascript
import { FlexRow, FlexColumn } from 'sonar';
// Align children horizontally
<FlexRow>
{children}
</FlexRow>
// Align children vertically
<FlexColumn>
{children}
</FlexColumn>
```
To control other flexbox properties than the direction you can extend existing components, detailed in [Styling Components](styling-components.md).
```javascript
import {FlexRow, styled} from 'sonar';
const CenterFlexRow = FlexRow.extends({
justifyContent: 'center',
});
// Align children horizontally in the center
<CenterFlexRow>{children}</CenterFlexRow>;
```
## Text
The `Text` component is available to render any text in your plugin. To render headers and subtitle differently for example, we used the styled module. With this we can also change the color, text alignment, and any other properties typically found on a `span`.
```javascript
import {Text, styled, colors} from 'sonar';
const Title = Text.extends({
color: colors.red,
});
<Title code={true}>Sonar Subtitle</Title>;
```
## Buttons
Sonar comes with a couple of button styles built in! As always you can style then further using the styled module but we expect the pre-defined buttons to fit most UIs.
```javascript
import {Button} from 'sonar';
<Button onClick={this.onClick} icon="airport" compact={true}>
Click Me!
</Button>;
```
You can create a group of buttons by surrounding it with `<ButtonGroup>`.
## Sidebar
The `Sidebar` component provides a nice abstraction around some common but complex behavior. The `Sidebar` is used by all major Sonar plugins and using it in your plugin will ensure your plugin behaves similarly, such as allowing for resizing.
```javascript
import {FlexRow, Sidebar, colors, styled} from 'infinity-ui';
import {SonarPlugin} from 'sonar';
type State = {};
export default class MySonarPlugin extends SonarPlugin<State> {
static title = 'My Plugin';
static id = 'my-plugin';
static Red = styled.view({
backgroundColor: colors.red,
});
static Blue = styled.view({
backgroundColor: colors.blue,
});
render() {
return (
<FlexRow fill={true}>
<MySonarPlugin.Red fill={true} />
<Sidebar position="right" width={400} minWidth={300}>
<MySonarPlugin.Blue fill={true} />
</Sidebar>
</FlexRow>
);
}
}
```
## Panel
Panels are a way to section data, and make it collapsible. They are often used in sidebars. Just give the Panel a heading and some content and it makes sure that it displays in the same style as the rest of Sonar.
```javascript
import {
FlexColumn,
FlexRow,
Sidebar,
Panel,
colors,
styled,
SonarPlugin,
} from 'sonar';
type State = {};
export default class MySonarPlugin extends SonarPlugin<State> {
static title = 'My Plugin';
static id = 'my-plugin';
static Red = styled.view({
backgroundColor: colors.red,
});
static Blue = styled.view({
backgroundColor: colors.blue,
height: 200,
});
static Green = styled.view({
backgroundColor: colors.green,
height: 200,
});
render() {
return (
<FlexRow fill={true}>
<MySonarPlugin.Red fill={true} />
<Sidebar position="right" width={400} minWidth={300}>
<FlexColumn>
<Panel heading={'Blue'} floating={false}>
<MySonarPlugin.Blue />
</Panel>
<Panel heading={'Green'} floating={false}>
<MySonarPlugin.Green />
</Panel>
</FlexColumn>
</Sidebar>
</FlexRow>
);
}
}
```
## DataInspector
The `DataInspector` component is used to unpack and display a javascript object. It is used to show View properties in the layout inspector, and to show event data in the analytics plugins.
```javascript
import {FlexColumn, DataInspector, SonarPlugin} from 'sonar';
type State = {};
export default class MySonarPlugin extends SonarPlugin<State> {
static title = 'My Plugin';
static id = 'my-plugin';
static data = {
one: 1,
two: '2',
three: [1, 2, 3],
};
render() {
return (
<FlexColumn fill={true}>
<DataInspector data={MySonarPlugin.data} />
</FlexColumn>
);
}
}
```
## Toolbar
The `Toolbar` component can display a toolbar with buttons, inputs, etc. A `<Spacer />` can be used to fill the space between items.
```javascript
import {Toolbar, Spacer, Button, SonarPlugin} from 'sonar';
export default class MySonarPlugin extends SonarPlugin<State> {
render() {
return (
<Toolbar fill={true}>
<Button>Button A</Button>
<Spacer />
<Button>Button B</Button>
</Toolbar>
);
}
}
```
## Popover
Used to display content in an overlay.
```javascript
import {Popover, SonarPlugin} from 'sonar';
export default class MySonarPlugin extends SonarPlugin<State> {
render() {
return (
{this.state.popoverVisible && <Popover onDismiss={() => this.setState({popoverVisible: false})}>
...
</Popover >}
);
}
}
```
## ContextMenu
Add a native context menu to a component by wrapping it with the ContextMenu component.
```javascript
import {ContextMenu, SonarPlugin} from 'sonar';
export default class MySonarPlugin extends SonarPlugin<State> {
contextMenuItems = [
{
label: 'Copy',
click: this.copy,
},
{
type: 'separator',
},
{
label: 'Clear All',
click: this.clear,
},
];
render() {
return <ContextMenu items={this.contextMenuItems}>...</ContextMenu>;
}
}
```

View File

@@ -0,0 +1,13 @@
---
id: understand
title: Understanding Sonar
sidebar_label: Understanding Sonar
---
The Sonar desktop app and the mobile native SDK establish an [rsocket](http://rsocket.io) connection which is used to send data to and from the device. Sonar does not make any restrictions on what kind of data is being sent. This enables a lot of different use-cases where you want to better understand what is going inside your app. For example you can visualize the state of local caches, events happening or trigger actions on your app from the desktop.
## Plugins
Sonar itself only provides the architectural platform. What makes it useful are the plugins built on top of it: [Logs](logs-plugin.md), [Layout Inspector](layout-plugin.md) and [Network Inspector](network-plugin.md) are all plugins. Plugins can be built very specific to your business logic and the use-cases you have in your app. We are shipping Sonar with a couple of built-in all-purpose plugins, but we encourage you to build your own.
A plugin always consists of the native implementation sending and receiving data and the desktop plugin visualizing data. Learn more on how to [create a plugin](create-plugin.md). The native implementations are written in Java, Objective-C, or C++, the desktop UI is written in React.