Skip to content

Commit

Permalink
fix(composer): show sync matterport tag status
Browse files Browse the repository at this point in the history
  • Loading branch information
sheilaXu committed Sep 21, 2023
1 parent 4b38ea5 commit 2c041c5
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ describe('MatterportTagSync', () => {
(createSceneRootEntity as jest.Mock).mockImplementation(() => {
return Promise.resolve({ entityId: 'scene-root-id' });
});
(prepareWorkspace as jest.Mock).mockResolvedValue(undefined);

getScenePropertyMock.mockImplementation((p) => {
if (p == KnownSceneProperty.MatterportModelId) {
Expand All @@ -258,6 +259,30 @@ describe('MatterportTagSync', () => {
setTwinMakerSceneMetadataModule({} as TwinMakerSceneMetadataModule);
});

it('should render success status', async () => {
useStore('default').setState(baseState);
const rendered = render(<MatterportTagSync />);

await act(async () => {
fireEvent.click(rendered.getByTestId('matterport-tag-sync-button'));
});

expect(rendered.getByTestId('sync-button-status')).toMatchSnapshot();
});

it('should render failure status', async () => {
useStore('default').setState(baseState);
(prepareWorkspace as jest.Mock).mockRejectedValue(new Error('prepare workspace failed'));

const rendered = render(<MatterportTagSync />);

await act(async () => {
fireEvent.click(rendered.getByTestId('matterport-tag-sync-button'));
});

expect(rendered.getByTestId('sync-button-status')).toMatchSnapshot();
});

it('should call prepareWorkspace', async () => {
useStore('default').setState(baseState);
const rendered = render(<MatterportTagSync />);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { useIntl } from 'react-intl';
import { Button, Popover, StatusIndicator } from '@awsui/components-react';
import { isEmpty } from 'lodash';
Expand Down Expand Up @@ -40,101 +40,100 @@ export const MatterportTagSync: React.FC = () => {

const { handleAddMatterportTag, handleUpdateMatterportTag, handleRemoveMatterportTag } = useMatterportTags();
const { mattertagObserver, tagObserver } = useMatterportObserver();
const [syncTagStatus, setSyncTagStatus] = useState<'loading' | 'success' | 'error'>('loading');

const doSync = useCallback(async () => {
const layerName = MATTERPORT_TAG_LAYER_PREFIX + matterportModelId;
let layerId = sceneLayerIds?.at(0);
let rootId = sceneRootId;
const sceneMetadataModule = getGlobalSettings().twinMakerSceneMetadataModule;

if (dynamicSceneEnabled && sceneMetadataModule) {
// Before backend can create the new default roots, the client side code will
// create them temporarily here.
await prepareWorkspace(sceneMetadataModule);

// Create default layer and default scene root node
if (!layerId || isEmpty(layerId) || !(await checkIfEntityAvailable(layerId, sceneMetadataModule))) {
try {
setSyncTagStatus('loading');

try {
const layerName = MATTERPORT_TAG_LAYER_PREFIX + matterportModelId;
let layerId = sceneLayerIds?.at(0);
let rootId = sceneRootId;
const sceneMetadataModule = getGlobalSettings().twinMakerSceneMetadataModule;

if (dynamicSceneEnabled && sceneMetadataModule) {
// Before backend can create the new default roots, the client side code will
// create them temporarily here.
await prepareWorkspace(sceneMetadataModule);

// Create default layer and default scene root node
if (!layerId || isEmpty(layerId) || !(await checkIfEntityAvailable(layerId, sceneMetadataModule))) {
const layer = await createLayer(layerName, LayerType.Relationship);
layerId = layer?.entityId;
setSceneProperty(KnownSceneProperty.LayerIds, [layerId]);
} catch (e) {
logger?.error('Create layer entity error', e);
// TODO: error handling
return;
}
}
if (!sceneRootId || isEmpty(sceneRootId) || !(await checkIfEntityAvailable(sceneRootId, sceneMetadataModule))) {
try {
if (!sceneRootId || isEmpty(sceneRootId) || !(await checkIfEntityAvailable(sceneRootId, sceneMetadataModule))) {
const root = await createSceneRootEntity();
rootId = root?.entityId;
setSceneProperty(KnownSceneProperty.SceneRootEntityId, rootId);
} catch (e) {
logger?.error('Create scene root entity error', e);
// TODO: error handling
return;
}
}
}

const mattertags = mattertagObserver.getMattertags(); // Matterport tags v1
const tags = tagObserver.getTags(); // Matterport tags v2
const mattertags = mattertagObserver.getMattertags(); // Matterport tags v1
const tags = tagObserver.getTags(); // Matterport tags v2

const tagRecords = getComponentRefByType(KnownComponentType.Tag);
const oldTagMap: Record<string, { nodeRef: string; node: ISceneNodeInternal }> = {};
for (const key in tagRecords) {
const node = getSceneNodeByRef(key);
if (node?.properties.matterportId) {
oldTagMap[node.properties.matterportId as string] = { nodeRef: key, node: node };
const tagRecords = getComponentRefByType(KnownComponentType.Tag);
const oldTagMap: Record<string, { nodeRef: string; node: ISceneNodeInternal }> = {};
for (const key in tagRecords) {
const node = getSceneNodeByRef(key);
if (node?.properties.matterportId) {
oldTagMap[node.properties.matterportId as string] = { nodeRef: key, node: node };
}
}
}

// Process Matterport tags v2
if (tags) {
for (const [key, value] of tags) {
if (oldTagMap[key]) {
await handleUpdateMatterportTag({
layerId,
sceneRootId: rootId,
ref: oldTagMap[key].nodeRef,
node: oldTagMap[key].node,
item: value,
});
} else {
await handleAddMatterportTag({ layerId, sceneRootId: rootId, id: key, item: value });
// Process Matterport tags v2
if (tags) {
for (const [key, value] of tags) {
if (oldTagMap[key]) {
await handleUpdateMatterportTag({
layerId,
sceneRootId: rootId,
ref: oldTagMap[key].nodeRef,
node: oldTagMap[key].node,
item: value,
});
} else {
await handleAddMatterportTag({ layerId, sceneRootId: rootId, id: key, item: value });
}
delete oldTagMap[key];
}
delete oldTagMap[key];
}
}

// Process Matterport tags v1
if (mattertags) {
// matterport dictionary is a customer matterport type that doesn't inherit other javascript properties like
// enumerable so use this exact iterable for it only
for (const [key, value] of mattertags) {
// Process only those tags which are not already taken care by v2 tags version
if (tags && tags[key]) {
continue;
}
// Process Matterport tags v1
if (mattertags) {
// matterport dictionary is a customer matterport type that doesn't inherit other javascript properties like
// enumerable so use this exact iterable for it only
for (const [key, value] of mattertags) {
// Process only those tags which are not already taken care by v2 tags version
if (tags && tags[key]) {
continue;
}

if (oldTagMap[key]) {
await handleUpdateMatterportTag({
layerId,
sceneRootId: rootId,
ref: oldTagMap[key].nodeRef,
node: oldTagMap[key].node,
item: value,
});
} else {
await handleAddMatterportTag({ layerId, sceneRootId: rootId, id: key, item: value });
if (oldTagMap[key]) {
await handleUpdateMatterportTag({
layerId,
sceneRootId: rootId,
ref: oldTagMap[key].nodeRef,
node: oldTagMap[key].node,
item: value,
});
} else {
await handleAddMatterportTag({ layerId, sceneRootId: rootId, id: key, item: value });
}
delete oldTagMap[key];
}
delete oldTagMap[key];
}
}

for (const key in oldTagMap) {
handleRemoveMatterportTag(oldTagMap[key].nodeRef);
for (const key in oldTagMap) {
await handleRemoveMatterportTag(oldTagMap[key].nodeRef);
}
} catch (e) {
logger?.error(String(e));
setSyncTagStatus('error');
return;
}

setSyncTagStatus('success');
}, [
getSceneNodeByRef,
mattertagObserver,
Expand All @@ -156,8 +155,12 @@ export const MatterportTagSync: React.FC = () => {
size='small'
triggerType='custom'
content={
<StatusIndicator type='success'>
{intl.formatMessage({ defaultMessage: 'Tags sync successful', description: 'Status indicator label' })}
<StatusIndicator type={syncTagStatus} data-testid='sync-button-status'>
{syncTagStatus === 'success'
? intl.formatMessage({ defaultMessage: 'Tags sync successful', description: 'Status indicator label' })
: syncTagStatus === 'error'
? intl.formatMessage({ defaultMessage: 'Tags sync failed', description: 'Status indicator label' })
: intl.formatMessage({ defaultMessage: 'Syncing tags', description: 'Status indicator label' })}
</StatusIndicator>
}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ exports[`MatterportTagSync should render correctly 1`] = `
<div>
<div
data-mocked="StatusIndicator"
type="success"
data-testid="sync-button-status"
type="loading"
>
Tags sync successful
Syncing tags
</div>
</div>
<div
Expand All @@ -24,3 +25,23 @@ exports[`MatterportTagSync should render correctly 1`] = `
</div>
</div>
`;

exports[`MatterportTagSync when dynamic scene is enabled should render failure status 1`] = `
<div
data-mocked="StatusIndicator"
data-testid="sync-button-status"
type="error"
>
Tags sync failed
</div>
`;

exports[`MatterportTagSync when dynamic scene is enabled should render success status 1`] = `
<div
data-mocked="StatusIndicator"
data-testid="sync-button-status"
type="success"
>
Tags sync successful
</div>
`;
34 changes: 7 additions & 27 deletions packages/scene-composer/src/hooks/useMatterportTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,7 @@ const addTag = async (
Node: nodeComp,
},
};
try {
await sceneMetadataModule.createSceneEntity(creatEntity);
} catch (e) {
console.error('Create scene node entity failed', e);
}
await sceneMetadataModule.createSceneEntity(creatEntity);
}
addSceneNode(node, true);
};
Expand Down Expand Up @@ -187,11 +183,7 @@ const updateTag = async (
updateEntity.componentUpdates![KnownComponentType.DataOverlay] = overlayComp;
}

try {
await sceneMetadataModule.updateSceneEntity(updateEntity);
} catch (e) {
console.error('Update scene node entity failed', e);
}
await sceneMetadataModule.updateSceneEntity(updateEntity);
} else {
const createEntity: CreateEntityCommandInput = {
workspaceId: undefined,
Expand All @@ -208,11 +200,7 @@ const updateTag = async (
if (overlayComp) {
createEntity.components![KnownComponentType.DataOverlay] = overlayComp;
}
try {
await sceneMetadataModule.createSceneEntity(createEntity);
} catch (e) {
console.error('Create scene node entity failed', e);
}
await sceneMetadataModule.createSceneEntity(createEntity);
}
}

Expand Down Expand Up @@ -245,7 +233,7 @@ type UpdateTagInputs = {
const useMatterportTags = (): {
handleAddMatterportTag: (inputs: AddTagInputs) => Promise<void>;
handleUpdateMatterportTag: (inputs: UpdateTagInputs) => Promise<void>;
handleRemoveMatterportTag: (nodeRef: string) => void;
handleRemoveMatterportTag: (nodeRef: string) => Promise<void>;
} => {
const sceneComposerId = useSceneComposerId();
const appendSceneNode = useStore(sceneComposerId)((state) => state.appendSceneNode);
Expand All @@ -268,20 +256,12 @@ const useMatterportTags = (): {
);

const handleRemoveMatterportTag = useCallback(
(nodeRef: string) => {
async (nodeRef: string) => {
const sceneMetadataModule = getGlobalSettings().twinMakerSceneMetadataModule;
if (dynamicSceneEnabled && sceneMetadataModule) {
sceneMetadataModule
.deleteSceneEntity({ entityId: nodeRef })
.then((_) => {
removeSceneNode(nodeRef);
})
.catch((e) => {
console.error('Delete scene node entity failed', e);
});
} else {
removeSceneNode(nodeRef);
await sceneMetadataModule.deleteSceneEntity({ entityId: nodeRef });
}
removeSceneNode(nodeRef);
},
[removeSceneNode],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,10 @@
"note": "Form Field label",
"text": "Model Type"
},
"V1T3We": {
"note": "Status indicator label",
"text": "Syncing tags"
},
"VObiQu": {
"note": "option text of a Select component",
"text": "Arrow with background"
Expand Down Expand Up @@ -907,6 +911,10 @@
"note": "placeholder",
"text": "Choose a rule"
},
"osIcJX": {
"note": "Status indicator label",
"text": "Tags sync failed"
},
"pyg21P": {
"note": "Number of Triangles in a scene",
"text": "Triangles : {sceneInfoTriangles, number}"
Expand Down

0 comments on commit 2c041c5

Please sign in to comment.