Skip to content

Commit

Permalink
Add support for expanding embedded and link objects.
Browse files Browse the repository at this point in the history
  • Loading branch information
gagik committed Jan 13, 2023
1 parent fed70fd commit 3ed3adc
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 41 deletions.
54 changes: 29 additions & 25 deletions flipper-plugin-realm/src/components/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { PlusOutlined } from '@ant-design/icons';
import { Button, Table } from 'antd';
import {
ColumnsType,
ExpandableConfig,
FilterValue,
SorterResult,
TablePaginationConfig,
Expand All @@ -25,10 +26,9 @@ export type ColumnType = {


type DataTableProps = {
columns: ColumnType[];
objects: DeserializedRealmObject[];
schemas: SortedObjectSchema[];
currentSchema: Realm.CanonicalObjectSchema;
currentSchema: SortedObjectSchema;
sortingDirection?: 'ascend' | 'descend' | null;
sortingColumn: string | null;
generateMenuItems?: MenuItemGenerator;
Expand All @@ -46,9 +46,10 @@ type DataTableProps = {
wipeStacks?: boolean,
) => void;
clickAction?: (object: DeserializedRealmObject) => void;
getObject: (object: RealmObjectReference, objectSchemaName: string) => Promise<DeserializedRealmObject | null>;
};

type ClickableTextType = {
type ClickableTextProps = {
displayText: string | number | JSX.Element;
isLongString: boolean;
value: PlainRealmObject | RealmObjectReference;
Expand All @@ -57,7 +58,7 @@ type ClickableTextType = {
};

// Receives a schema and returns column objects for the table.
export const schemaObjToColumns = (
const schemaObjectToColumns = (
schema: SortedObjectSchema,
): ColumnType[] => {
return schema.order.map((propertyName) => {
Expand All @@ -75,7 +76,6 @@ export const schemaObjToColumns = (

export const DataTable = (dataTableProps: DataTableProps) => {
const {
columns,
objects,
schemas,
currentSchema,
Expand All @@ -90,6 +90,7 @@ export const DataTable = (dataTableProps: DataTableProps) => {
totalObjects = 0,
fetchMore = () => undefined,
clickAction,
getObject,
} = dataTableProps;
const instance = usePlugin(plugin);
const state = useValue(instance.state);
Expand All @@ -108,13 +109,14 @@ export const DataTable = (dataTableProps: DataTableProps) => {
expandedRowRender: () => {
return <></>;
},
expandedRowKeys: [],
showExpandColumn: false,
});
} as ExpandableConfig<DeserializedRealmObject>);

/** Hook to close the nested Table when clicked outside of it. */
useEffect(() => {
const closeNestedTable = () => {
setRowExpansionProp({ ...rowExpansionProp });
setRowExpansionProp({ ...rowExpansionProp, expandedRowKeys: [] });
};
document.body.addEventListener('click', closeNestedTable);
return () => document.body.removeEventListener('click', closeNestedTable);
Expand All @@ -139,7 +141,7 @@ export const DataTable = (dataTableProps: DataTableProps) => {
value,
inspectorView,
isReference = false,
}: ClickableTextType) => {
}: ClickableTextProps) => {
const [isHovering, setHovering] = useState(false);
return (
<div>
Expand Down Expand Up @@ -171,20 +173,18 @@ export const DataTable = (dataTableProps: DataTableProps) => {
};

/** Definition of antd-specific columns. This constant is passed to the antd table as a property. */
const antdColumns:ColumnsType<DeserializedRealmObject> = columns.map((column) => {
const antdColumns:ColumnsType<DeserializedRealmObject> = schemaObjectToColumns(currentSchema).map((column) => {
const property: Realm.CanonicalObjectSchemaProperty =
currentSchema.properties[column.name];

const linkedSchema = schemas.find(
(schema) => property && schema.name === property.objectType,
);
/* A function that is applied for every cell to specify what to render in each cell
on top of the pure value specified in the 'dataSource' property of the antd table.*/
const render = (value: PlainRealmObject, row: DeserializedRealmObject) => {
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 linkedSchema = schemas.find(
(schema) => schema.name === property.objectType,
);

/** 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') {
const isEmbedded = linkedSchema.embedded;
Expand All @@ -197,21 +197,26 @@ export const DataTable = (dataTableProps: DataTableProps) => {
gap: '5px',
}}
>
<Button
{/* Embedded objects cannot be queried in the row. */
!isEmbedded && <Button
shape="circle"
type="primary"
size="small"
icon={<PlusOutlined />}
onClick={(event) => {
onClick={async (event) => {
event.stopPropagation();
expandRow(
row.objectKey,
linkedSchema,
value,
);
// Fetch the linked object and if found, expand the table with the row.
let linkedObject = await getObject(value as RealmObjectReference, linkedSchema.name)
if(linkedObject) {
expandRow(
row.objectKey,
linkedSchema,
linkedObject,
);
}
}}
ghost
/>
/>}
{
<ClickableText
value={
Expand Down Expand Up @@ -297,7 +302,7 @@ export const DataTable = (dataTableProps: DataTableProps) => {
const expandRow = (
rowToExpandKey: any,
linkedSchema: SortedObjectSchema,
objectToRender: PlainRealmObject,
objectToRender: DeserializedRealmObject,
) => {
const newRowExpansionProp = {
...rowExpansionProp,
Expand All @@ -308,7 +313,6 @@ export const DataTable = (dataTableProps: DataTableProps) => {
{ ...dataTableProps }
objects={[objectToRender]}
currentSchema={linkedSchema}

/>
);
},
Expand Down
23 changes: 17 additions & 6 deletions flipper-plugin-realm/src/components/RealmDataInspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {
CloseOutlined,
SearchOutlined,
} from '@ant-design/icons';
import { Button, Col, Layout, Radio, Row, Space, Tooltip } from 'antd';
import { Button, Col, Layout, Radio, Row, Space, Tag, Tooltip } from 'antd';
import { DataInspector, DetailSidebar, Spinner } from 'flipper-plugin';
import React, { useEffect, useState } from 'react';
import { PlainRealmObject, RealmObjectReference } from '../CommonTypes';
import { DeserializedRealmObject, PlainRealmObject, RealmObjectReference } from '../CommonTypes';
import { BoldSpan } from './SchemaSelect';

export type InspectionDataType = {
Expand All @@ -30,7 +30,7 @@ type RealmDataInspectorProps = {
goForwardStack: Array<InspectionDataType>;
setGoForwardStack: React.Dispatch<React.SetStateAction<InspectionDataType[]>>;
setNewInspectionData: (newInspectionData: InspectionDataType) => void;
getObject: (object: RealmObjectReference) => Promise<PlainRealmObject | null>;
getObject: (object: RealmObjectReference) => Promise<DeserializedRealmObject | null>;
};

// Helper function to traverse through a Realm object given a path
Expand Down Expand Up @@ -72,7 +72,7 @@ export const RealmDataInspector = ({
setInspectionData({
data: {
[inspectionData.data.objectType as string]:
loadedObject,
loadedObject.realmObject,
},
view: inspectionData.view,
isReference: false,
Expand All @@ -90,6 +90,16 @@ export const RealmDataInspector = ({
backgroundColor: flickering ? '#6932c9' : 'transparent',
};

// // TODO: not sure if this is best way to go about this.
// let formattedObjects: any = {};
// Object.entries(inspectionData.data).forEach(([field, fieldValue]) => {
// if (typeof fieldValue === "object" && fieldValue.objectKey && fieldValue.objectType) {
// formattedObjects[field] = `[${fieldValue.objectType}]._objectKey=${fieldValue.objectKey}`
// } else {
// formattedObjects[field] = fieldValue;
// }
// })

return (
<DetailSidebar>
<Space direction="vertical" size="middle" style={flickerStyle}>
Expand Down Expand Up @@ -160,7 +170,7 @@ export const RealmDataInspector = ({
if(linkedSchema) {
ownSchema = schemas.find(
(
innerSchema // And there exists some non-embedded schema that fits the objectType of that property
innerSchema // And there exists some schema that fits the objectType of that property
) =>
linkedSchema && linkedSchema.properties[name].objectType ===
innerSchema.name
Expand All @@ -174,7 +184,8 @@ export const RealmDataInspector = ({
return (
<>
{name + ' '}
<Tooltip title="Inspect Linked Object" placement="topLeft">
<Tag color="processing">Ref</Tag>
<Tooltip title="Inspect Referenced Object" placement="topLeft">
<Button
shape="circle"
type="primary"
Expand Down
4 changes: 2 additions & 2 deletions flipper-plugin-realm/src/pages/DataVisualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { DropdownPropertyType, MenuItemGenerator, DeserializedRealmObject, Sorte
import {
CustomDropdown,
} from '../components/CustomDropdown';
import { DataTable, schemaObjToColumns } from '../components/DataTable';
import { DataTable } from '../components/DataTable';
import { FieldEdit } from '../components/objectManipulation/FieldEdit';
import { ObjectEdit } from '../components/objectManipulation/ObjectEdit';
import {
Expand Down Expand Up @@ -241,7 +241,6 @@ const DataVisualizer = ({
/>
) : null}
<DataTable
columns={schemaObjToColumns(currentSchema)}
objects={objects}
schemas={schemas}
hasMore={hasMore}
Expand All @@ -258,6 +257,7 @@ const DataVisualizer = ({
setNewInspectionData={setNewInspectionData}
fetchMore={fetchMore}
clickAction={clickAction}
getObject={(object: RealmObjectReference, schemaName: string) => {return getObject(selectedRealm, schemaName, object.objectKey)}}
/>
<CustomDropdown {...updatedDropdownProp} />
<RealmDataInspector
Expand Down
20 changes: 13 additions & 7 deletions realm-flipper-plugin-device/src/ConvertFunctions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ export interface SerializedRealmObject extends RealmObjectReference {
realmObject: any;
}

export const serializeRealmObject = (
realmObject: Realm.Object,
objectSchema: ObjectSchema,
): SerializedRealmObject => {
/** Helper to recursively serialize Realm objects and embedded objects into plain JavaScript objects. */
const serializeObject = (realmObject: RealmObject, objectSchema: Realm.ObjectSchema): Record<string, unknown> => {
const properties = objectSchema.properties;
const jsonifiedObject = realmObject.toJSON();

Expand All @@ -51,10 +49,10 @@ export const serializeRealmObject = (
const isEmbedded = (propertyValue.objectSchema() as ObjectSchema).embedded
if (!isEmbedded) {
// If the object is linked (not embedded), store only the object key and type
// as a seperate key for later plugin lazy loading
// as a seperate key for later plugin lazy loading reference
jsonifiedObject[key] = {objectKey, objectType} as SerializedRealmObject;
} else {
jsonifiedObject[key] = serializeRealmObject(jsonifiedObject[key] as Realm.Object, propertyValue.objectSchema());
jsonifiedObject[key] = serializeObject(propertyValue as Realm.Object, propertyValue.objectSchema());
}
break;
case "data":
Expand All @@ -68,10 +66,18 @@ export const serializeRealmObject = (
}
}
});
return jsonifiedObject;
}

/** Serialized a given Realm Object into a SerializedRealmObject, providing circular dependency safe format. */
export const serializeRealmObject = (
realmObject: Realm.Object,
objectSchema: ObjectSchema,
): SerializedRealmObject => {
return {
objectKey: realmObject._objectKey(),
// flatted.toJSON is used to ensure circular objects can get stringified by Flutter.
realmObject: toJSON(jsonifiedObject),
realmObject: toJSON(serializeObject(realmObject, objectSchema)),
};
};

Expand Down
13 changes: 12 additions & 1 deletion testApp/app/flipperTest/testData/createCornerCaseData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,29 @@ export function createCornerCaseData(realm: Realm) {
var nestedObject: any = realm.create('NestedObject', {
_id: 0,
} as any);

const cornerCase: any = {
embedded: {
items: ['one', 'two', 'three'],
},
mixed: null,
indirectObject: nestedObject,
};
// cornerCase.embedded.indirectCycle = cornerCase;

nestedObject.nestedObject = nestedObject;
let cornerCaseObject: any = realm.create('CornerCase', cornerCase);
nestedObject.cornerCase = cornerCaseObject;
cornerCaseObject.indirectCycle = nestedObject;
cornerCaseObject.directCycle = cornerCaseObject;

const cornerCaseB: any = {
embedded: {
items: [],
},
mixed: null,
};
let cornerCaseObjectB: any = realm.create('CornerCase', cornerCaseB);

cornerCaseObject.embedded.embeddedReference = cornerCaseObjectB;
});
}

0 comments on commit 3ed3adc

Please sign in to comment.