Skip to content

Commit

Permalink
Support . in dialog name (microsoft#208)
Browse files Browse the repository at this point in the history
* use [] for dialog name path

* add # to split dialog name and path

* UpperCase file name

* fix test

* handle value change

* add filename check
  • Loading branch information
boydc2014 authored Apr 26, 2019
1 parent 5bff755 commit 6507cca
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 61 deletions.
24 changes: 7 additions & 17 deletions Composer/packages/client/__tests__/utils/dialogUtil.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getDialogData, setDialogData, getRootDialogName } from '../../src/utils/dialogUtil';
import { getDialogData, setDialogData } from '../../src/utils/dialogUtil';

const dialogsMap = {
Dialog1: {
Expand All @@ -23,46 +23,36 @@ const dialogsMap = {
},
};

describe('getRootDialogName', () => {
it('returns the dialog name for the given path', () => {
let result = getRootDialogName(dialogsMap, 'Dialog2.main');
expect(result).toEqual('Dialog2.main');

result = getRootDialogName(dialogsMap, 'Dialog2.main.steps[1]');
expect(result).toEqual('Dialog2.main');
});
});

describe('getDialogData', () => {
it('returns all dialog data if path is a top level property', () => {
const result = getDialogData(dialogsMap, 'Dialog1');
const result = getDialogData(dialogsMap, 'Dialog1#');
expect(result).toEqual(dialogsMap.Dialog1);
});

it('returns all dialog data if path is a top level property and has a "."', () => {
const result = getDialogData(dialogsMap, 'Dialog2.main');
const result = getDialogData(dialogsMap, 'Dialog2.main#');
expect(result).toEqual(dialogsMap['Dialog2.main']);
});

it('returns a sub path', () => {
const result = getDialogData(dialogsMap, 'Dialog1.steps[1]');
const result = getDialogData(dialogsMap, 'Dialog1#steps[1]');
expect(result).toEqual(dialogsMap.Dialog1.steps[1]);
});

it('returns a sub path when "." is in path', () => {
const result = getDialogData(dialogsMap, 'Dialog2.main.steps[1]');
const result = getDialogData(dialogsMap, 'Dialog2.main#steps[1]');
expect(result).toEqual(dialogsMap['Dialog2.main'].steps[1]);
});
});

describe('setDialogData', () => {
it('returns updated top level dialog data', () => {
const result = setDialogData(dialogsMap, 'Dialog2.main', { new: 'data' });
const result = setDialogData(dialogsMap, 'Dialog2.main#', { new: 'data' });
expect(result).toEqual({ new: 'data' });
});

it('returns updated dialog data at a path', () => {
const result = setDialogData(dialogsMap, 'Dialog2.main.steps[1]', { new: 'data' });
const result = setDialogData(dialogsMap, 'Dialog2.main#steps[1]', { new: 'data' });
expect(result).toEqual({ steps: [{ $type: 'Step3' }, { new: 'data' }] });
});
});
4 changes: 2 additions & 2 deletions Composer/packages/client/src/ShellApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import debounce from 'lodash.debounce';

import { Store } from './store/index';
import ApiClient from './messenger/ApiClient';
import { getDialogData, setDialogData, getRootDialogName } from './utils';
import { getDialogData, setDialogData } from './utils';
// this is the api interface provided by shell to extensions
// this is the single place handles all incoming request from extensions, VisualDesigner or FormEditor
// this is where all side effects (like directly calling api of extensions) happened
Expand Down Expand Up @@ -78,7 +78,7 @@ export function ShellApi() {
return;
} else if (sourceWindowName === 'FormEditor') {
const updatedDialog = setDialogData(dialogsMap, focusPath, newData);
const dialogName = getRootDialogName(dialogsMap, focusPath);
const dialogName = focusPath.split('#')[0];
const payload = {
name: dialogName,
content: updatedDialog,
Expand Down
14 changes: 3 additions & 11 deletions Composer/packages/client/src/components/ProjectTree/folder.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { PropTypes } from 'prop-types';
import formatMessage from 'format-message';

import { folderItem } from './styles';
import { upperCaseName } from './../../utils/fileUtil';

export const Folder = props => {
const { activeNode, folder, onFolderClick, onFolderRightClick } = props;

function splitFileName(text) {
const pattern = /\.{1}[a-zA-Z]{1,}$/;
let temp = text;
if (pattern.exec(text) !== null) {
temp = text.slice(0, pattern.exec(text).index);
}

return temp.charAt(0).toUpperCase() + temp.slice(1);
}

return (
<div css={folderItem(activeNode === folder.id)}>
<span onClick={() => onFolderClick(folder)} onContextMenu={onFolderRightClick}>
{splitFileName(folder.name)}
{formatMessage(upperCaseName(folder.name))}
</span>
</div>
);
Expand Down
11 changes: 6 additions & 5 deletions Composer/packages/client/src/pages/design/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { Fragment, useContext, useState, useMemo } from 'react';
import { Breadcrumb, IconButton } from 'office-ui-fabric-react';
import startCase from 'lodash.startcase';
import findindex from 'lodash.findindex';
import formatMessage from 'format-message';

Expand All @@ -12,6 +11,7 @@ import { ProjectTree } from './../../components/ProjectTree';
import { Store } from './../../store/index';
import { breadcrumbClass } from './styles';
import NewDialogModal from './NewDialogModal';
import { upperCaseName } from './../../utils/fileUtil';

function DesignPage() {
const { state, actions } = useContext(Store);
Expand All @@ -21,7 +21,7 @@ function DesignPage() {

function handleFileClick(index) {
clearNavHistory();
navTo(dialogs[index].name);
navTo(`${dialogs[index].name}#`);
}

const dialogsMap = useMemo(() => {
Expand All @@ -33,12 +33,13 @@ function DesignPage() {

const breadcrumbItems = useMemo(() => {
return navPathHistory.map((item, index) => {
const text = item.indexOf('.') > -1 ? getDialogData(dialogsMap, `${item}.$type`) : item;
const pathList = item.split('#');
const text = pathList[1] === '' ? pathList[0] : getDialogData(dialogsMap, `${item}#$type`);

return {
key: index,
path: item,
text: formatMessage(startCase(text).replace(/\s/g, '')),
text: formatMessage(upperCaseName(text)),
onClick: (_event, { path, key }) => {
clearNavHistory(key);
navTo(path);
Expand All @@ -49,7 +50,7 @@ function DesignPage() {

const activeDialog = useMemo(() => {
if (!navPath) return -1;
const dialogName = navPath.split('.')[0];
const dialogName = navPath.split('#')[0];
return findindex(dialogs, { name: dialogName });
}, [navPath]);

Expand Down
8 changes: 4 additions & 4 deletions Composer/packages/client/src/store/action/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export async function fetchProject(dispatch) {
payload: { response },
});
if (dialogs && dialogs.length > 0) {
navTo(dispatch, dialogs[0].name);
navTo(dispatch, `${dialogs[0].name}#`);
}
} catch (err) {
dispatch({
Expand All @@ -36,7 +36,7 @@ export async function openBotProject(dispatch, storageId, absolutePath) {
payload: { response },
});
if (dialogs && dialogs.length > 0) {
navTo(dispatch, dialogs[0].name);
navTo(dispatch, `${dialogs[0].name}#`);
}
} catch (err) {
dispatch({
Expand All @@ -60,7 +60,7 @@ export async function saveProjectAs(dispatch, storageId, absolutePath) {
payload: { response },
});
if (dialogs && dialogs.length > 0) {
navTo(dispatch, dialogs[0].name);
navTo(dispatch, `${dialogs[0].name}#`);
}
} catch (err) {
dispatch({
Expand Down Expand Up @@ -112,7 +112,7 @@ export async function createDialog(dispatch, { name, steps }) {
});
// the new dialog only has 1 rule, so navigate directly there
clearNavHistory(dispatch);
navTo(dispatch, `${name}.rules[0]`);
navTo(dispatch, `[${name}]#rules[0]`);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
Expand Down
13 changes: 11 additions & 2 deletions Composer/packages/client/src/store/reducer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ const navigateTo = (state, { path }) => {
};

const navigateDown = (state, { subPath }) => {
state.navPath = state.navPath + subPath;
if (state.navPath.endsWith('#')) {
state.navPath = state.navPath + subPath.substring(1);
} else {
state.navPath = state.navPath + subPath;
}
state.focusPath = state.navPath; // fire up form editor on non-leaf node
state.navPathHistory.push(state.navPath);
return state;
Expand All @@ -92,7 +96,12 @@ const focusTo = (state, { subPath }) => {
if (state.focusPath !== state.navPath + subPath) {
state.resetFormEditor = true;
}
return (state.focusPath = state.navPath + subPath);
if (state.navPath.endsWith('#')) {
state.focusPath = state.navPath + subPath.substring(1);
} else {
state.focusPath = state.navPath + subPath;
}
return state.focusPath;
};

const clearNavHistory = (state, { fromIndex }) => {
Expand Down
30 changes: 11 additions & 19 deletions Composer/packages/client/src/utils/dialogUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,25 @@ import get from 'lodash.get';
import set from 'lodash.set';
import cloneDeep from 'lodash.clonedeep';

export function getRootDialogName(dialogsMap, path) {
return Object.keys(dialogsMap).find(d => path.startsWith(d));
}

export function getDialogData(dialogsMap, path) {
// if path is a top level name, return the whole dialog
if (dialogsMap.hasOwnProperty(path)) {
return dialogsMap[path];
}
if (path === '') return '';
const pathList = path.split('#');
const dialog = dialogsMap[pathList[0]];

const dialog = getRootDialogName(dialogsMap, path);
const data = dialogsMap[dialog];
const subPath = path.replace(`${dialog}.`, '');
if (pathList[1] === '') {
return dialog;
}

return get(data, subPath);
return get(dialog, pathList[1]);
}

export function setDialogData(dialogsMap, path, data) {
const dialogsMapClone = cloneDeep(dialogsMap);
const pathList = path.split('#');
const dialog = dialogsMapClone[pathList[0]];

if (dialogsMapClone.hasOwnProperty(path)) {
if (pathList[1] === '') {
return data;
}

const dialog = getRootDialogName(dialogsMapClone, path);
const dialogData = dialogsMapClone[dialog];
const subPath = path.replace(`${dialog}.`, '');

return set(dialogData, subPath, data);
return set(dialog, pathList[1], data);
}
5 changes: 5 additions & 0 deletions Composer/packages/client/src/utils/fileUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ export function getBaseName(filename) {
if (typeof filename !== 'string') return filename;
return filename.substring(0, filename.lastIndexOf('.')) || filename;
}

export function upperCaseName(filename) {
if (typeof filename !== 'string') return filename;
return filename.charAt(0).toUpperCase() + filename.slice(1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class IntentRule extends React.Component {
}}
onClick={e => {
e.stopPropagation();
this.props.onEvent(NodeClickActionTypes.OpenLink, normalizedStep.dialog);
this.props.onEvent(NodeClickActionTypes.OpenLink, normalizedStep.dialog + '#');
}}
>
{normalizedStep.dialog}
Expand Down

0 comments on commit 6507cca

Please sign in to comment.