Skip to content

Commit

Permalink
feat: ds ga
Browse files Browse the repository at this point in the history
1. enable loading query results with child nodes.
2. remove dependent layer concept.
  • Loading branch information
Chen Shang authored and haweston committed May 2, 2024
1 parent b0fa5c1 commit b503fcf
Show file tree
Hide file tree
Showing 17 changed files with 113 additions and 356 deletions.
2 changes: 1 addition & 1 deletion packages/scene-composer/package.json
Expand Up @@ -46,7 +46,7 @@
"convert-svg": "npx @svgr/cli --out-dir src/assets/auto-gen/icons/ --typescript --index-template tools/index-template.js -- src/assets/icons/",
"release": "run-s compile copy-assets",
"copy-assets": "copyfiles -e \"**/*.tsx\" -e \"**/*.ts\" -e \"**/*.snap\" -e \"**/*.js\" -e \"**/*.jsx\" -e \"**/*.json\" \"src/**/*\" dist/",
"lint": "eslint . --max-warnings=478",
"lint": "eslint . --max-warnings=481",
"fix": "eslint --fix .",
"test": "jest --config jest.config.ts --coverage --silent",
"test:reliability": "npm run test:ui:reliability",
Expand Down
1 change: 1 addition & 0 deletions packages/scene-composer/src/SceneViewer.tsx
Expand Up @@ -73,6 +73,7 @@ export const SceneViewer: React.FC<SceneViewerProps> = ({ sceneComposerId, confi
...((config as any)?.featureConfig || {}),
[COMPOSER_FEATURES.Matterport]: true,
[COMPOSER_FEATURES.SceneAppearance]: true,
[COMPOSER_FEATURES.DynamicScene]: true,
},
}}
onSceneLoaded={onSceneLoaded}
Expand Down
Expand Up @@ -19,7 +19,7 @@ exports[`SceneViewer should render correctly 1`] = `
data-testid="webgl-root"
>
<div
config="{\\"mode\\":\\"Viewing\\",\\"featureConfig\\":{\\"Matterport\\":true,\\"SceneAppearance\\":true}}"
config="{\\"mode\\":\\"Viewing\\",\\"featureConfig\\":{\\"Matterport\\":true,\\"SceneAppearance\\":true,\\"DynamicScene\\":true}}"
data-mocked="SceneComposerInternal"
onSceneLoaded={[Function]}
sceneComposerId="123"
Expand Down
1 change: 1 addition & 0 deletions packages/scene-composer/src/common/entityModelConstants.ts
Expand Up @@ -37,6 +37,7 @@ export const MATTERPORT_TAG_LAYER_PREFIX = 'Matterport_Tag_';
export const DEFAULT_LAYER_RELATIONSHIP_NAME = 'inLayerOf';
export const DEFAULT_LAYER_COMPONENT_NAME = 'Layer';
export const LAYER_ROOT_ENTITY_ID = 'LAYERS_EntityId';
export const RESERVED_LAYER_ID = 'reserved_layer_id';
export const LAYER_ROOT_ENTITY_NAME = '$LAYERS';
export enum LayerType {
Relationship = 'Relationship',
Expand Down
29 changes: 5 additions & 24 deletions packages/scene-composer/src/components/SceneLayers.spec.tsx
@@ -1,3 +1,4 @@
/* eslint-disable */
import * as React from 'react';
import { render } from '@testing-library/react';
import { useQuery } from '@tanstack/react-query';
Expand All @@ -6,7 +7,7 @@ import { useStore } from '../store';
import { processQueries } from '../utils/entityModelUtils/processQueries';
import { KnownSceneProperty } from '../interfaces';
import { LAYER_DEFAULT_REFRESH_INTERVAL } from '../utils/entityModelUtils/sceneLayerUtils';

import { RESERVED_LAYER_ID } from '../common/entityModelConstants';
import { SceneLayers } from './SceneLayers';

jest.mock('../utils/entityModelUtils/processQueries', () => ({
Expand Down Expand Up @@ -40,20 +41,7 @@ describe('SceneLayers', () => {
});
});

it('should enable query when have layerId', async () => {
useStore('default').setState(baseState);
let enabledValue = false;
(useQuery as jest.Mock).mockImplementation(({ enabled, ..._ }) => {
enabledValue = enabled;
return {};
});

render(<SceneLayers />);

expect(enabledValue).toBe(true);
});

it('should not enable query when no layerId', async () => {
it('should enable query even no layerId specified', async () => {
useStore('default').setState(baseState);
getScenePropertyMock.mockReturnValue([]);

Expand All @@ -65,7 +53,7 @@ describe('SceneLayers', () => {

render(<SceneLayers />);

expect(enabledValue).toBe(false);
expect(enabledValue).toBe(true);
});

it('should not refetch when not in viewing mode', async () => {
Expand Down Expand Up @@ -141,13 +129,6 @@ describe('SceneLayers', () => {
await queryFunction();

expect(processQueries as jest.Mock).toBeCalledTimes(1);
expect(processQueries as jest.Mock).toBeCalledWith(
[
expect.stringContaining("AND e.entityId = 'layer1'"),
expect.stringContaining(`MATCH (binding)<-[r2]-(entity)-[r]->(e)`),
],
expect.anything(),
);
expect(renderSceneNodesFromLayersMock).not.toBeCalled();
});

Expand All @@ -164,6 +145,6 @@ describe('SceneLayers', () => {

expect(processQueries as jest.Mock).toBeCalledTimes(1);
expect(renderSceneNodesFromLayersMock).toBeCalledTimes(1);
expect(renderSceneNodesFromLayersMock).toBeCalledWith(['random'], 'layer1');
expect(renderSceneNodesFromLayersMock).toBeCalledWith(['random'], RESERVED_LAYER_ID);
});
});
68 changes: 40 additions & 28 deletions packages/scene-composer/src/components/SceneLayers.tsx
Expand Up @@ -6,11 +6,7 @@ import { sceneComposerIdContext } from '../common/sceneComposerIdContext';
import { useStore } from '../store';
import { processQueries } from '../utils/entityModelUtils/processQueries';
import { KnownSceneProperty } from '../interfaces';
import {
DEFAULT_ENTITY_BINDING_RELATIONSHIP_NAME,
DEFAULT_LAYER_RELATIONSHIP_NAME,
SUB_MODEL_REF_PARENT_RELATIONSHIP_NAME,
} from '../common/entityModelConstants';
import { DEFAULT_PARENT_RELATIONSHIP_NAME, RESERVED_LAYER_ID } from '../common/entityModelConstants';

export const SceneLayers: React.FC = () => {
const sceneComposerId = useContext(sceneComposerIdContext);
Expand All @@ -20,32 +16,48 @@ export const SceneLayers: React.FC = () => {
);

const renderSceneNodesFromLayers = useStore(sceneComposerId)((state) => state.renderSceneNodesFromLayers);
const layerIds = useStore(sceneComposerId)((state) => state.getSceneProperty<string[]>(KnownSceneProperty.LayerIds));
const layerId = layerIds?.[0];
const layerId = RESERVED_LAYER_ID;

const sceneRootEntityId = useStore(sceneComposerId)((state) =>
state.getSceneProperty(KnownSceneProperty.SceneRootEntityId),
);

const nodes = useQuery({
enabled: !isEmpty(layerIds),
queryKey: ['scene-layers', layerIds, sceneComposerId],
enabled: !!sceneRootEntityId,
queryKey: ['scene-layers', sceneRootEntityId, sceneComposerId],
queryFn: async () => {
const nodes = await processQueries(
[
// Get node entities in the layer
`SELECT entity, r, e
FROM EntityGraph
MATCH (entity)-[r]->(e)
WHERE r.relationshipName = '${DEFAULT_LAYER_RELATIONSHIP_NAME}'
AND e.entityId = '${layerId}'`,
// Get entityBinding and subModel parentRef for the nodes in the layer
`SELECT entity, r2, binding
FROM EntityGraph
MATCH (binding)<-[r2]-(entity)-[r]->(e)
WHERE r.relationshipName = '${DEFAULT_LAYER_RELATIONSHIP_NAME}'
AND e.entityId = '${layerId}'
AND (r2.relationshipName = '${DEFAULT_ENTITY_BINDING_RELATIONSHIP_NAME}'
OR r2.relationshipName = '${SUB_MODEL_REF_PARENT_RELATIONSHIP_NAME}')`,
],
(node) => (node.properties.layerIds = [...(node.properties.layerIds ?? []), layerId!]),
);
const nodes = await processQueries([
// Get node entities under the sceneRootEntityId
`select entity, r, e
from EntityGraph
match (entity)-[r]->(e)
where r.relationshipName = '${DEFAULT_PARENT_RELATIONSHIP_NAME}'
and e.entityId = '${sceneRootEntityId}'`,

`select c, r1, entity
from EntityGraph
match (c)-[r1]->(entity)-[r]->(e)
where r.relationshipName = '${DEFAULT_PARENT_RELATIONSHIP_NAME}'
and e.entityId = '${sceneRootEntityId}'`,

`select c2, r2, c
from EntityGraph
match (c2)-[r2]->(c)-[r1]->(entity)-[r]->(e)
where r.relationshipName = '${DEFAULT_PARENT_RELATIONSHIP_NAME}'
and e.entityId = '${sceneRootEntityId}'`,

`select c3, r3, c2
from EntityGraph
match (c3)-[r3]->(c2)-[r2]->(c)-[r1]->(entity)-[r]->(e)
where r.relationshipName = '${DEFAULT_PARENT_RELATIONSHIP_NAME}'
and e.entityId = '${sceneRootEntityId}'`,

`select c4, r4, c3
from EntityGraph
match (c4)-[r4]->(c3)-[r3]->(c2)-[r2]->(c)-[r1]->(entity)-[r]->(e)
where r.relationshipName = '${DEFAULT_PARENT_RELATIONSHIP_NAME}'
and e.entityId = '${sceneRootEntityId}'`,
]);
return nodes;
},
refetchInterval: (_, query) => {
Expand Down
21 changes: 8 additions & 13 deletions packages/scene-composer/src/components/StateManager.spec.tsx
Expand Up @@ -265,8 +265,6 @@ describe('StateManager', () => {
capabilities: [SceneCapabilities.DYNAMIC_SCENE],
sceneMetadata: { [SceneMetadataMapKeys.SCENE_ROOT_ENTITY_ID]: 'root-id' },
});
const mockSceneContent = 'mock scene content';
(parseSceneCompFromEntity as jest.Mock).mockReturnValue(mockSceneContent);

let container;
await act(async () => {
Expand All @@ -286,9 +284,8 @@ describe('StateManager', () => {
});

expect(container).toMatchSnapshot();
expect(mockSceneLoader.getSceneUri).not.toBeCalled();
expect(mockSceneMetadataModule.getSceneEntity).toBeCalledTimes(1);
expect(mockSceneMetadataModule.getSceneEntity).toBeCalledWith({ entityId: 'root-id' });
expect(mockSceneLoader.getSceneUri).toBeCalled();
expect(mockSceneMetadataModule.getSceneEntity).not.toBeCalled();
expect(baseState.loadScene).toBeCalledTimes(1);
expect(baseState.loadScene).toBeCalledWith(mockSceneContent, { disableMotionIndicator: false });
});
Expand Down Expand Up @@ -319,10 +316,9 @@ describe('StateManager', () => {
});

expect(container).toMatchSnapshot();
expect(mockSceneLoader.getSceneUri).not.toBeCalled();
expect(mockSceneMetadataModule.getSceneEntity).toBeCalledTimes(1);
expect(mockSceneMetadataModule.getSceneEntity).toBeCalledWith({ entityId: 'root-id' });
expect(baseState.loadScene).not.toBeCalled();
expect(mockSceneLoader.getSceneUri).toBeCalled();
expect(mockSceneMetadataModule.getSceneEntity).not.toBeCalled();
expect(baseState.loadScene).toBeCalled();
});

it('should load dynamic scene with error for getting scene entity failure', async () => {
Expand Down Expand Up @@ -353,10 +349,9 @@ describe('StateManager', () => {
});

expect(container).toMatchSnapshot();
expect(mockSceneLoader.getSceneUri).not.toBeCalled();
expect(mockSceneMetadataModule.getSceneEntity).toBeCalledTimes(1);
expect(mockSceneMetadataModule.getSceneEntity).toBeCalledWith({ entityId: 'root-id' });
expect(baseState.loadScene).not.toBeCalled();
expect(mockSceneLoader.getSceneUri).toBeCalled();
expect(mockSceneMetadataModule.getSceneEntity).not.toBeCalled();
expect(baseState.loadScene).toBeCalled();
});

it('should load dynamic scene with error for getting scene info failure', async () => {
Expand Down
28 changes: 16 additions & 12 deletions packages/scene-composer/src/components/StateManager.tsx
Expand Up @@ -108,6 +108,7 @@ const StateManager: React.FC<SceneComposerInternalProps> = ({
() => createStandardUriModifier(sceneContentUri || '', undefined),
[sceneContentUri],
);
const [sceneRootEntityId, setSceneRootEntityId] = useState(null);

const isViewing = config.mode === 'Viewing';

Expand Down Expand Up @@ -245,22 +246,21 @@ const StateManager: React.FC<SceneComposerInternalProps> = ({
return;
}

if (sceneInfo && sceneMetadataModule && sceneInfo.capabilities?.includes(SceneCapabilities.DYNAMIC_SCENE)) {
// Fetch scene level metadata for dynamic scene
const rootId = sceneInfo.sceneMetadata?.[SceneMetadataMapKeys.SCENE_ROOT_ENTITY_ID];
if (!rootId) {
setLoadSceneError(new Error('Failed to get scene root entity id'));
return;
if (sceneRootEntityId && sceneInfo && sceneMetadataModule) {
if (sceneInfo.contentLocation && sceneInfo.contentLocation.length > 0) {
setSceneContentUri(sceneInfo.contentLocation!);
}

sceneMetadataModule
.getSceneEntity({ entityId: rootId })
.getSceneEntity({ entityId: sceneRootEntityId })
.then((res) => {
const document = parseSceneCompFromEntity(res);
if (!document) {
throw new Error('Failed to parse scene metadata');
if (res?.components?.length) {
const document = parseSceneCompFromEntity(res);
if (!document) {
throw new Error('Failed to parse scene metadata');
}
setSceneContent(document);
}
setSceneContent(document);
})
.catch((e) => {
setLoadSceneError(e || new Error('Failed to get scene root entity'));
Expand All @@ -282,7 +282,7 @@ const StateManager: React.FC<SceneComposerInternalProps> = ({
.catch((error) => {
setLoadSceneError(error || new Error('Failed to get scene uri'));
});
}, [sceneLoader, sceneMetadataModule, sceneInfo]);
}, [sceneLoader, sceneMetadataModule, sceneInfo, sceneRootEntityId]);

useEffect(() => {
if (sceneContentUri && sceneContentUri.length > 0) {
Expand Down Expand Up @@ -369,6 +369,10 @@ const StateManager: React.FC<SceneComposerInternalProps> = ({
if (!state.sceneLoaded || !old.sceneLoaded || state.document === old.document) {
return;
}
const rootId = state.document.properties?.sceneRootEntityId ?? null;
if (rootId) {
setSceneRootEntityId(rootId);
}
onSceneUpdated(sceneDocumentSnapshotCreator.create({ document: state.document }));
});
}
Expand Down

0 comments on commit b503fcf

Please sign in to comment.