Summary:
This diff makes sure that devices will actually instantiate applicable sandy device plugins. Similar to how client plugins are owned by Client, device plugins are directly owned by BaseDevice, which significantly simplifies life cycle management and doesn't dispatch updates to all Redux connect components whenever something irrelevant changes.
Also made sure `device.teardown()` is called. That API already existed, but wasn't used or implemented before.
Updated Flipper test utils to support testing device plugins as well (both Sandy and classic ones)
Reviewed By: passy, nikoant
Differential Revision: D22693929
fbshipit-source-id: 73b2b8666ef7a0e748ea89360db84734d37eb5be
Summary: Add unit tests to verify that the unit test utilities for for Sandy device plugins work as expected. Fixed a bug revealed by that and cleaned up some TODO's
Reviewed By: jknoxville, passy, nikoant
Differential Revision: D22693928
fbshipit-source-id: 93162db19d826d0cd7f642cef1447fd756261ac8
Summary:
This stack introduces Sandy device plugins, they are quite similar to normal plugins, but, a devicePlugin module is organized as
```
export function supportsDevice(device): boolean
export function devicePlugin(devicePluginClient)
export function Component
```
Device plugins get access to the device meta data and can subscribe to the `onLogEntry` callback and `onDestroy` lifecycle.
They will be able to store state just as normal plugins, but can't send or receive methods, so devicePluginClient is a bit limited.
This diff only sets up most of the new data structures, and makes sure everything still compiles and no existing tests fail.
To prevent this diff from becoming to big, actually loading, rendering and testing device plugins will be done in next diffs
Please take a critical look at the api proposed and the (especially) the public names used :)
Reviewed By: passy, nikoant
Differential Revision: D22691351
fbshipit-source-id: bdbbd7f86d14b646fc9a693ad19f33583a76f26d
Summary:
This adds support for handling incoming deeplinks in a Sandy plugin, which can be done by using a `client.onDeepLink(deepLink => { } )` listener
Also generalized deeplinks to not just support strings, but also richer objects, which is beneficial to plugin to plugin linking.
Reviewed By: jknoxville
Differential Revision: D22524749
fbshipit-source-id: 2cbe8d52f6eac91a1c1c8c8494706952920b9181
Summary:
This diff makes sure Sandy plugins show up as well in the plugin selector when making exports (and in support form as well).
Also verified that this works with the Sandy updated section plugin.
Note that persisted state now didn't need any changes in the plugin code to work :)
Commented / simplified the calculation of available plugins a little bit and fixed a confusing issue where two different redux stores where created in one unit test, which caused an issue in the new implementation.
Reviewed By: jknoxville
Differential Revision: D22434301
fbshipit-source-id: c911196bc5b105309e82204188f124f40aab487a
Summary:
Made Sandy plugins part of the export / import logic
Note that in the export format there is now a `pluginStates2` field. It could have been put in `pluginStates` as well, but I started with that and it felt very weird, as the `pluginStates` are supposed to reflect what is living in the Redux stores, where sandy state doesn't live in the Redux store (not directly at least). Trying to store it in the same field made things overcomplicated and confusing.
Reviewed By: jknoxville
Differential Revision: D22432769
fbshipit-source-id: d9858e561fad4fadbf0f90858874ed444e3a836c
Summary:
This plugin adds serialization capabilities to Sandy plugins buy setting the a `persist: <key>` flag. This shouldn't be used for state that is unserializable, too big, too sensitive, or irrelevant during export / import.
Using an explicit `persist` flag is done to make plugins robust to changes over time; as long as the key is kept the same, state variables can be renamed and reordered without breaking the import / export format. Also it allows us to detect some changes in the import / export format and warn about it.
Alternative designs considered but not implemented would be:
1. requiring the user to explicitly return the state from the factory (e.g. `const todos = createState([]); return { todos }`,
2. or construct the state from client (e.g. `const todos = client.createState([])`)
3. enable persistence by default, and store states in the order the states were created (much like useState in React). This was implemented in the first versions of this diff, but as pointed out in the discussions, this is too sensitive too (accidental) format changes, as the storage format would be quite implicit
A nice benefit of the current approach, especially compared with alternative approach 1, is that state being restored is immediately visible in the plugin factory. In other words, directly after initialization `const todos = createState([])`, the `todos.get()` is actually set to the state that is being restored, rather than having still the initial state which is only overridden rather. So this behaves very much like the `useState` hook in React.
Furthermore, in the future we could use the same `persist` key in combination with other options, such as `saveToLocalStorage`, in case some state acts as a 'preference' (T69989583).
`TestUtils.startPlugin` supports starting plugins with an initial state by using the optional `initialState` field
Actually wiring up the serialization and deserialization into the export / import functionality of Flipper is done in the next diff.
Reviewed By: jknoxville
Differential Revision: D22432770
fbshipit-source-id: 9a4849582c2f6f54d1e40f65a6cba73092c28fe8
Summary:
Created a few breakages. Would appreciate some closer eyes on this.
Pull Request resolved: https://github.com/facebook/flipper/pull/1366
Reviewed By: mweststrate
Differential Revision: D22501454
fbshipit-source-id: 9b882a12aecc65da85f29101bf87bf27519a7d2a
Summary:
Converted the Seammammals plugin to use Sandy plugin infra (but the old components). Also updated lock file.
Added unittests have been added as well. The UI snapshots in there are kinda overkill, but nice demo that it works.
This completes the full roundtrip of the new Sandy infra, so this will be the last diff of this stack. I promise. I think.
Reviewed By: nikoant
Differential Revision: D22308265
fbshipit-source-id: 260e91a1951d486f6689880fe25281e80a71806a
Summary: While testing manually discovered the sandy plugin infra din't cover the case that a plugin can be selected but not enabled at the same time. Added test and fixed that.
Reviewed By: nikoant
Differential Revision: D22308597
fbshipit-source-id: 6cef2b543013ee81cee449396d523dd9a657ad1c
Summary:
Introduced a minimal state abstraction so that state can be maintained in the plugin instance, but subscribe to by the UI.
At a later point we could pick an off the shelve solution like Recoil or MobX, or introduce cursors to read something deep etc etc, but for now that doesn't seem to be needed at all, and I think this will be pretty comprehensible for plugin authors (see also the 25th diff in this stack).
The api
```
createState(initialValue): Atom
Atom {
get() // returns current value
set(newValue) // sets a new value
update(draft => { }) // updates a complex value using Immer behind the scenes
}
// hook, subscribes to the updates of the Atom and always returns the current value
useValue(atom)
```
Reviewed By: nikoant
Differential Revision: D22306673
fbshipit-source-id: c49f5af85ae9929187e4d8a051311a07c1b88eb5
Summary:
`usePlugin(pluginFactory)` returns the current plugin instance's api that was exposed by the plugin directory.
Passing `pluginFactory` is technically strictly not needed, but having the user pass it, we can make sure it is strongly typed
Reviewed By: nikoant
Differential Revision: D22286293
fbshipit-source-id: 4268b6849b8cd3d524103de7eadbd6c0a65c7a61
Summary: This diffs adds the capability to listen to messages in Sandy plugins. Although API wise it looks more like the old `this.subscribe`, semantically it behaves like the `persistedStateReducer`; messages are queued if the plugin is enabled but not active.
Reviewed By: nikoant
Differential Revision: D22282711
fbshipit-source-id: 885faa702fe779ac8d593c1d224b2be13e688d47
Summary: Sandy plugins can now send messages to plugins. The methods and params are strongly typed in implementation and unit tests, based on the <Methods> generic of FlipperClient.
Reviewed By: nikoant
Differential Revision: D22256972
fbshipit-source-id: 549523a402949b3eb6bb4b4ca160dedb5c5e722d
Summary:
Upgrading to TypeScript 3.9 so that we can use `// ts-expect-error`, that allows us to basically write unit tests for typings.
Fixed the typescript path in `settings.json`, because otherwise VSCode kept picking the built-in version (3.8.3 atm) of ts when opening the `sonar` folder as workspace.
Reviewed By: passy
Differential Revision: D22255818
fbshipit-source-id: ae54aedb237445f32f1797b4290656dbfb0e528f
Summary:
This sets up the initial infra that is to be used by plugin devs to test plugins.
There is not much yet to see, as there is no state or message sending yet. But at least the life cycle of plugins can be test, things are strongly typed and everything is in the place where it should be :)
N.b. the import difference with these utils and the createFlipperMock utilities in Flipper are
1. this testing infra is entirely inside flipper-plugin package, so that plugin devs don't need flipper as a dependency
2. this testing infra doesn't provide abstractions for plugin / device / client switching; it tests plugins purely in isolation of the rest of the world (except for firing `onConnect` / `onDisconnect` which is normally the effect of switching plugins)
Reviewed By: nikoant
Differential Revision: D22255262
fbshipit-source-id: b94ccbab720d2b49428a646aed3c55af71a5bc80
Summary:
Introduced hooks that are called whenever the plugin is connected / disconnected to it's counter part on the device.
There is some logic duplication between `PluginContainer` for old plugins, and `PluginRenderer` for new plugins, mostly caused by the fact that those lifecycles are triggered from the UI rather than from the reducers, but I figured refactoring that to be too risky.
Reviewed By: jknoxville
Differential Revision: D22232337
fbshipit-source-id: a384c45731a4c8d9b8b532a83e2becf49ce807c2
Summary: PluginContainer will now wrap Sandy plugins in PluginRenderer. PluginRenderer will also be used by plugin unit tests in the future
Reviewed By: jknoxville
Differential Revision: D22159359
fbshipit-source-id: 69f9c8f4bec9392022c1d7a14957f5aca0339d97
Summary: This diff introduces the `onDestroy` hook that can be used by plugins to listen to the event where a plugin is cleaned up (either because it is disabled, or because the client is being cleaned up)
Reviewed By: jknoxville
Differential Revision: D22208121
fbshipit-source-id: 9c4951ae671be611f21da171c548d4054c481166
Summary:
This diff makes sure sandy plugins are initialized.
Sandy plugins are stored directly in the client for two reasons
1. we want to decouple any plugin state updates from our central redux store, as redux is particularly bad in handling high frequency updates.
2. The lifecycle of a plugin is now bound to the lifecycle of a client. This means that we don't need 'persistedStore' to make sure state is preserved; that is now the default. Switching plugins will no longer reinitialize them (but only run specific hooks, see later diffs).
3. PersistedState will be introduced for sandy plugins as well later, but primarily for import / export / debug reasons.
A significant difference with the current persistent state, is that if a client crashes and reconnects, plugins will loose their state. We can prevent this (again, since state persisting will be reintroduced), but I'm not sure we need that for the specific reconnect scenario. Because
1. we should fix reconnecting clients anyway, and from stats it looks to happen less already
2. reconnects are usually caused by plugins that aggregate a lot of data and get slower over time. Restoring old state also restores those unstabilites.
For the overview bringing back the archi picture of earlier diff:
{F241508042}
Also fixed a bug where enabling background plugins didn't enable them on all devices with that app.
Reviewed By: jknoxville
Differential Revision: D22186276
fbshipit-source-id: 3fd42b577f86920e5280aa8cce1a0bc4d5564ed9
Summary: Make sure Sandy plugins are loaded properly from disk
Reviewed By: jknoxville
Differential Revision: D22186275
fbshipit-source-id: fd2f560a7bed959b18e05db2a087909ad876ab9d
Summary:
So far there were 2 types of plugins: `FlipperPlugin` and `FlipperDevicePlugin`. This introduces a third kind: `SandyPluginDefinition`.
Unlike with the old plugins, the export of the module is not directly exposed as the plugin definition. Rather, we use class `SandyPluginDefinition` (instance) that holds a loaded definition and its meta data separately (`PluginDetails`). This means that we don't have to mix in and mutate loaded definitions, and that for unit tests we can avoid needing to provide a bunch of meta data. This also prevents a bunch of meta data existing on two places: on the loaded classes as static fields, and in the meta data field of the loaded class as well. Finally, we can now freely extends the `PluginDetails` interface in flipper, without needing to store it on the loaded classes and we are sure that no naming conflicts are caused by this in the future.
For compatibility with the existing code base, common fields are delegated from the `SandyPluginDefinition` class to the meta data.
Also cleaned up types around plugins a little bit and removed some unnecessary casts.
For all features that reason about plugins in general (such as exports), sandy plugins are ignored for now.
`SandyPluginInstance` is worked out in further diffs
The `instanceof` calls are replaced by a utility function in later diffs.
{F241363645}
Reviewed By: jknoxville
Differential Revision: D22091432
fbshipit-source-id: 3aa6b12fda5925268913779f3c3c9e84494438f8
Summary: Set up an initial library which can (should) be used by plugins in the future.
Reviewed By: jknoxville
Differential Revision: D22019554
fbshipit-source-id: 502b14b34b2c9c117cea377ab6ebbf150e6faee9