Skip to content

Commit

Permalink
Add better list and mixed support.
Browse files Browse the repository at this point in the history
  • Loading branch information
gagik committed Jan 17, 2023
1 parent 2e66f28 commit 536e421
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 72 deletions.
2 changes: 1 addition & 1 deletion flipper-plugin-realm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
"name": "realm-flipper-plugin",
"id": "realm",
"version": "1.0.14",
"version": "1.1.0",
"pluginType": "client",
"description": "A Flipper Plugin to debug Realm applications.",
"main": "dist/bundle.js",
Expand Down
11 changes: 11 additions & 0 deletions flipper-plugin-realm/src/CommonTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ export interface CanonicalObjectSchemaPropertyRow
primaryKey: boolean;
}

export interface DeserializedRealmData {
length: number;
info: [string, string, string];
}

export interface DeserializedRealmDecimal128 {
$numberDecimal: string
}

export type DownloadDataFunction = (schema: string, objectKey: string, propertyName: string) => Promise<Uint8Array>;

export type RealmPluginState = {
deviceSerial: string;
realms: string[];
Expand Down
2 changes: 1 addition & 1 deletion flipper-plugin-realm/src/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export const DataTable = (dataTableProps: DataTableProps) => {
on top of the pure value specified in the 'dataSource' property of the antd table.*/
const render = (value: PlainRealmObject | RealmObjectReference, row: DeserializedRealmObject) => {
/** Apply the renderValue function on the value in the cell to create a standard cell. */
const cellValue = renderValue(value, property, schemas);
const cellValue = renderValue(value, property, schemas, instance.downloadData);

/** Render buttons to expand the row and a clickable text if the cell contains a linked or embedded Realm object. */
if (value !== null && linkedSchema && property.type === 'object') {
Expand Down
15 changes: 11 additions & 4 deletions flipper-plugin-realm/src/components/RealmDataInspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,22 +152,29 @@ export const RealmDataInspector = ({
expandRoot={true}
collapsed={false}
onRenderName={(path, name) => {
const nameAsIndex = Number(name);
// Check whether we are rendering a list item, i.e. object.listName[0]
const isCollectionItem = Number.isInteger(nameAsIndex) && path.length > 1
// TODO: Unsure if this is good enough to handle collection items.
const fieldName:string = isCollectionItem ? path.at(-2) as string : name;

// Finding out if the currently rendered value has a schema belonging to it and assigning it to linkedSchema
let ownSchema: Realm.CanonicalObjectSchema | undefined;
let linkedSchema: Realm.CanonicalObjectSchema | undefined = schemas.find(
(schema) => schema.properties[name]
(schema) => schema.properties[fieldName]
);
if(linkedSchema) {
ownSchema = schemas.find(
(
innerSchema // And there exists some schema that fits the objectType of that property
) =>
linkedSchema && linkedSchema.properties[name].objectType ===
linkedSchema && linkedSchema.properties[fieldName].objectType ===
innerSchema.name
)
}
// If there is a linked existing, non-embedded schema on the property then this is a linked object
const isLinkedObject = linkedSchema && ownSchema && !ownSchema.embedded
// If there is a linked existing, non-collection, non-embedded schema on the property then this is a linked object
const isCollection = !isCollectionItem && linkedSchema?.properties[fieldName].type == "list" || linkedSchema?.properties[fieldName].type == "set"
const isLinkedObject = linkedSchema && !isCollection && ownSchema && !ownSchema.embedded

// If this is a linked object field and there is a value assigned to it, add a clickable reference.
if (isLinkedObject && traverseThroughObject<RealmObjectReference>(inspectionData.data, path)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ export const ObjectEdit = ({
visible,
}: InputType) => {
const [value, setValue] = useState(initialObject);
// Use a different state for modification to prevent updating the global state on cancellation.
const [displayValue, setDisplayValue] = useState(structuredClone({...value}));
const { modifyObject } = usePlugin(plugin);
const [propsChanged, setPropsChanges] = useState<Set<string>>(new Set());

const onOk = () => {
modifyObject(value, propsChanged);
setValue(displayValue);
modifyObject(displayValue, propsChanged);
hideModal();
};

Expand All @@ -41,8 +43,8 @@ export const ObjectEdit = ({
>
<PropertiesModify
schema={schema}
value={value}
setValue={setValue}
value={displayValue}
setValue={setDisplayValue}
setPropsChanges={setPropsChanges}
/>
</Modal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const MixedInput = ({
const [value, setValue] = useState<unknown | undefined>(
defaultValue === null ? undefined : defaultValue
);
const { state } = usePlugin(plugin);
const { state, downloadData } = usePlugin(plugin);
const { schemas } = useValue(state);

const addObject = () => {
Expand Down Expand Up @@ -65,7 +65,7 @@ export const MixedInput = ({
<Row align="middle">
<Col flex="auto">
<Tag color="success">{chosenType}</Tag>
{renderValue(value, { type, objectType: objectType?.name }, schemas)}
{renderValue(value, { type, objectType: objectType?.name }, schemas, downloadData)}
</Col>
<Col>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ export const ObjectInput = ({
if(isEmbedded) {
content = ``;
}
else if (targetSchema?.primaryKey !== undefined) {
const val = serializedObject.realmObject[targetSchema.primaryKey];
content = `${targetSchema.primaryKey}: ${val}`;
}
// TODO: Fetch object with getObject to display primary key instead of objectKey
// else if (targetSchema?.primaryKey !== undefined) {
// const val = serializedObject.realmObject[targetSchema.primaryKey];
// content = `${targetSchema.primaryKey}: ${val}`;
// }
else {
const val = serializedObject.objectKey;
content = `_objectKey: ${val}`;
Expand Down Expand Up @@ -102,7 +103,6 @@ export const ObjectInput = ({
instance
.requestObjects(targetSchema.name, selectedRealm, undefined, cursor, '')
.then((response: GetObjectsResponse) => {
//@ts-expect-error TODO: Remove the need to downloadData here as this will be display-only.
setObjects([...objects, ...deserializeRealmObjects(response.objects, targetSchema)]);
setHasMore(response.hasMore);
setCursor(response.nextCursor);
Expand Down
6 changes: 2 additions & 4 deletions flipper-plugin-realm/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export function plugin(client: PluginClient<Events, Methods>) {
const addedObject = deserializeRealmObject(
clone,
state.currentSchema,
downloadData,
);
copyOfObjects.splice(index, 0, addedObject);
const newLastObject = copyOfObjects[copyOfObjects.length - 1];
Expand Down Expand Up @@ -150,7 +149,6 @@ export function plugin(client: PluginClient<Events, Methods>) {
const addedObject = deserializeRealmObject(
clone,
state.currentSchema,
downloadData,
);
copyOfObjects.splice(index, 1, addedObject);
const newLastObject = copyOfObjects[copyOfObjects.length - 1];
Expand Down Expand Up @@ -221,7 +219,7 @@ export function plugin(client: PluginClient<Events, Methods>) {
if (!actualSchema) {
return null;
}
const deserializedObject = deserializeRealmObject(serializedObject, actualSchema, downloadData);
const deserializedObject = deserializeRealmObject(serializedObject, actualSchema);
return deserializedObject;
},
(reason) => {
Expand Down Expand Up @@ -280,7 +278,6 @@ export function plugin(client: PluginClient<Events, Methods>) {
const objects = deserializeRealmObjects(
response.objects,
state.currentSchema,
downloadData,
);
pluginState.set({
...state,
Expand Down Expand Up @@ -566,6 +563,7 @@ export function plugin(client: PluginClient<Events, Methods>) {
refreshState,
clearError,
requestObjects,
downloadData,
};
}

Expand Down
15 changes: 2 additions & 13 deletions flipper-plugin-realm/src/utils/ConvertFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ import { DeserializedRealmObject, SerializedRealmObject } from '../CommonTypes';
export const deserializeRealmObject = (
receivedObject: SerializedRealmObject,
schema: Realm.CanonicalObjectSchema,
downloadData: (
schema: string,
objectKey: string,
propertyName: string,
) => Promise<Uint8Array>,
) => {
if(receivedObject.realmObject == undefined) {
return receivedObject;
Expand All @@ -26,8 +21,7 @@ export const deserializeRealmObject = (
if (property && property.type === 'data') {
convertedObject.realmObject[key] = {
length: (value as Record<'$binaryData', number>).$binaryData,
downloadData: () =>
downloadData(schema.name, receivedObject.objectKey, property.name),
info: [schema.name, receivedObject.objectKey, property.name],
};
} else {
convertedObject.realmObject[key] = value;
Expand All @@ -39,11 +33,6 @@ export const deserializeRealmObject = (
export const deserializeRealmObjects = (
serializedObjects: SerializedRealmObject[],
schema: Realm.CanonicalObjectSchema,
downloadData: (
schema: string,
objectKey: string,
propertyName: string,
) => Promise<Uint8Array>,
) => {
return serializedObjects.map((object) => deserializeRealmObject(object, schema, downloadData));
return serializedObjects.map((object) => deserializeRealmObject(object, schema));
};
56 changes: 27 additions & 29 deletions flipper-plugin-realm/src/utils/Renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import BooleanValue from '../components/BooleanValue';
import { Button, message, Typography } from 'antd';
import fileDownload from 'js-file-download';
import { CanonicalObjectSchema } from 'realm';
import { DeserializedRealmObject } from '../CommonTypes';
import { DeserializedRealmData, DeserializedRealmDecimal128, DeserializedRealmObject, DownloadDataFunction } from '../CommonTypes';
import { usePlugin } from 'flipper-plugin';
import { plugin } from '../index';

type TypeDescription = {
type: string;
Expand All @@ -14,6 +16,7 @@ export const renderValue = (
value: unknown,
property: TypeDescription,
schemas: CanonicalObjectSchema[],
downloadData: DownloadDataFunction,
inner?: boolean,
): JSX.Element | string | number => {
if (value === null) {
Expand All @@ -35,27 +38,26 @@ export const renderValue = (
case 'float':
case 'objectId':
case 'date':
case 'uuid': //@ts-expect-error
return parseSimpleData(value);
case 'bool': //@ts-expect-error
return parseBoolean(value);
case 'uuid':
return parseSimpleData(value as string | number);
case 'bool':
return parseBoolean(value as boolean);
case 'list':
case 'set': //@ts-expect-error
return parseSetOrList(value, property, schemas);
case 'data': //@ts-expect-error
return parseData(value);
case 'dictionary': //@ts-expect-error
return parseDictionary(value);
case 'decimal128': //@ts-expect-error
return parseDecimal128(value);
case 'set':
return parseSetOrList(value as Realm.Set<unknown>, property, schemas, downloadData);
case 'data':
return parseData(value as DeserializedRealmData, downloadData);
case 'dictionary':
return parseDictionary(value as Record<string, unknown>);
case 'decimal128':
return parseDecimal128(value as DeserializedRealmDecimal128);
case 'object':
// eslint-disable-next-line @typescript-eslint/no-shadow
schema = schemas.find((schema) => schema.name === property.objectType);
if(schema?.embedded) {
return `[${schema.name}]`
}
//@ts-expect-error
return parseLinkedObject(schema as Realm.ObjectSchema, value);
return parseLinkedObject(schema as Realm.ObjectSchema, value as DeserializedRealmObject);
case 'mixed':
return parseMixed(value);
default:
Expand All @@ -71,6 +73,7 @@ function parseSetOrList(
input: Realm.Set<unknown> | Realm.List<unknown>,
property: TypeDescription,
schemas: Realm.CanonicalObjectSchema[],
downloadData: DownloadDataFunction,
): string {
const output = input.map((value: unknown) => {
// check if the container holds objects
Expand All @@ -82,6 +85,7 @@ function parseSetOrList(
objectType: property.objectType,
},
schemas,
downloadData,
true,
);
}
Expand All @@ -92,6 +96,7 @@ function parseSetOrList(
type: property.objectType as string,
},
schemas,
downloadData,
true,
);
});
Expand All @@ -103,23 +108,16 @@ function parseDictionary(input: Record<string, unknown>): string {
return JSON.stringify(input);
}

function parseData(input: {
downloadData: () => Promise<{ data: Uint8Array }>;
length: number;
}) {
if (input.downloadData === undefined) {
function parseData(input: DeserializedRealmData,
downloadData: DownloadDataFunction,
) {
if (input.info === undefined) {
return <Typography.Text disabled>data</Typography.Text>;
}
/* Structure of binary data:
input: {
downloadData: () => Promise<{ data: Uint8Array }>,
length,
}
*/
const handleDownload = () => {
input.downloadData().then(
downloadData(input.info[0], input.info[1], input.info[2]).then(
(res) => {
fileDownload(new Uint8Array(res.data).buffer, 'data');
fileDownload(new Uint8Array(res).buffer, 'data');
},
(reason) => {
message.error('downloading failed', reason.message);
Expand All @@ -135,7 +133,7 @@ function parseBoolean(input: boolean): JSX.Element {
return <BooleanValue active={input} value={inputAsString} />;
}

function parseDecimal128(input: { $numberDecimal: string }): string {
function parseDecimal128(input: DeserializedRealmDecimal128): string {
return input.$numberDecimal ?? input;
}

Expand Down
14 changes: 14 additions & 0 deletions realm-flipper-plugin-device/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## v1.1.0

This version brings a number of major changes to plugin's functionality, improving the ability to display more complex types of Realm objects. Please make sure to update your `realm-flipper-plugin` to the new version as well to ensure compatibility.

### Enhancements
* Referenced objects are now lazy-loaded. By default, they will display their object key and type (just for internal reference) and their actual values can be seen when they are inspected. This brings performance improvements when loading many objects with references as well as better support for circular and deeply nested references.
* Embedded object support.

### Fixed
* Plugin component crashing when trying to display embedded objects.

### Compatibility
* `realm` >= v11
* `realm-flipper-plugin` >= v1.1.0
7 changes: 3 additions & 4 deletions realm-flipper-plugin-device/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "realm-flipper-plugin-device",
"version": "1.0.28",
"version": "1.1.0",
"description": "Device code for interaction with the Realm Flipper Plugin",
"main": "dist/index.js",
"types": "src/RealmPlugin.tsx",
Expand Down Expand Up @@ -39,11 +39,10 @@
"peerDependencies": {
"react": ">=17",
"react-native-flipper": ">=0.162.0",
"realm": ">=10.0.0"
"realm": ">=11.0.0"
},
"dependencies": {
"flatted": "^3.2.7",
"react-native-flipper": ">=0.162.0"
"flatted": "^3.2.7"
},
"devDependencies": {
"@types/react": "^18.0.25",
Expand Down
Loading

0 comments on commit 536e421

Please sign in to comment.