Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Move indexer to client and add file persistence layer #2348

Merged
merged 46 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6544b25
add file persistence layer
lei9444 Mar 24, 2020
ef246d8
Merge branch 'master' into single
lei9444 Mar 25, 2020
cce5b30
Merge branch 'master' of https://github.com/microsoft/BotFramework-Co…
lei9444 Mar 25, 2020
1168adc
fix some confilct
lei9444 Mar 25, 2020
d6af93a
Merge branch 'master' of https://github.com/microsoft/BotFramework-Co…
lei9444 Mar 26, 2020
8bdadeb
support lu publish
lei9444 Mar 26, 2020
4928188
add locale for create
lei9444 Mar 27, 2020
01aadfc
Merge branch 'master' of https://github.com/microsoft/BotFramework-Co…
lei9444 Mar 27, 2020
1bd86a7
update the undo/redo
lei9444 Mar 27, 2020
578f85d
Merge branch 'master' into single
lei9444 Mar 27, 2020
c978548
fix unit test
lei9444 Mar 27, 2020
584387d
handle error
lei9444 Mar 27, 2020
3022d87
update the resover
lei9444 Mar 27, 2020
80bb528
add args
lei9444 Mar 27, 2020
ffc88ef
remove some notes
lei9444 Mar 30, 2020
c109ad8
Merge branch 'master' of https://github.com/microsoft/BotFramework-Co…
lei9444 Mar 30, 2020
96674e7
fix create dialog from form
lei9444 Mar 30, 2020
7088a2b
fix find wrong root dialog
lei9444 Mar 30, 2020
766ae66
add error handler
lei9444 Mar 30, 2020
ca1d648
add locale when search the common lg file
lei9444 Mar 30, 2020
def5276
Merge branch 'master' of https://github.com/microsoft/BotFramework-Co…
lei9444 Mar 31, 2020
475564d
move file persistence from action to reducer
lei9444 Mar 31, 2020
a2a88e7
Merge branch 'master' of https://github.com/microsoft/BotFramework-Co…
lei9444 Mar 31, 2020
da0c867
fix some conflicts
lei9444 Mar 31, 2020
bf47c96
remove file persisten midleware
lei9444 Mar 31, 2020
9b67f82
update the navigate
lei9444 Mar 31, 2020
87fb474
Merge branch 'master' into single
a-b-r-o-w-n Mar 31, 2020
8d6523e
Merge branch 'master' of https://github.com/microsoft/BotFramework-Co…
lei9444 Apr 1, 2020
67a1ea4
fix some conflicts
lei9444 Apr 1, 2020
5bde531
change type->kind
lei9444 Apr 1, 2020
9f32d9b
fix conflict
lei9444 Apr 1, 2020
03caf6c
add unit test for persistence layer
lei9444 Apr 1, 2020
75d51ed
add some unit test
lei9444 Apr 1, 2020
3d96a19
add unit tests for lu publish status
lei9444 Apr 1, 2020
b89ac83
add unit test for file reloatedreducer
lei9444 Apr 1, 2020
f1ec0fd
error handling
lei9444 Apr 1, 2020
db9b0f2
Merge branch 'master' into single
lei9444 Apr 2, 2020
b9324e8
Merge branch 'master' into single
lei9444 Apr 2, 2020
01f4a07
wrap the reducer
lei9444 Apr 2, 2020
74d9c46
Merge branch 'master' into single
lei9444 Apr 2, 2020
493f918
refine the persistence
lei9444 Apr 3, 2020
29ef448
Merge branch 'single' of https://github.com/lei9444/BotFramework-Comp…
lei9444 Apr 3, 2020
ca3d82a
fix lint
lei9444 Apr 3, 2020
7fd099f
fix some spelling mistakes
lei9444 Apr 3, 2020
8af2322
Merge branch 'master' into single
yeze322 Apr 3, 2020
c60ae1e
Merge branch 'master' into single
cwhitten Apr 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import undoHistory from './../../../../src/store/middlewares/undo/history';

describe('test undo history', () => {
it('test undo history', async () => {
const mockStack1Undo = jest.fn((store, args) => args);
const mockStack1Redo = jest.fn((store, args) => args);
const mockStack2Undo = jest.fn((store, args) => args);
const mockStack2Redo = jest.fn((store, args) => args);
const mockStack1Undo = jest.fn((store, from, to) => to[0]);
const mockStack1Redo = jest.fn((store, from, to) => to[0]);
const mockStack2Undo = jest.fn((store, from, to) => to[0]);
const mockStack2Redo = jest.fn((store, from, to) => to[0]);
const stack1 = undoHistory.createStack(mockStack1Undo, mockStack1Redo);
const stack2 = undoHistory.createStack(mockStack2Undo, mockStack2Redo);
stack1.add(['t0']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,12 @@ import { reducer } from '../../../src/store/reducer/index';

const mockResponse = {
data: {
dialogs: ['test dialogs'],
lgFiles: ['test lgFiles'],
luFiles: ['test luFiles'],
files: ['test files'],
schemas: 'test schemas',
},
};

describe('test all reducer handlers', () => {
it('test getProjectSuccess reducer', () => {
const result = reducer({}, { type: ActionTypes.GET_PROJECT_SUCCESS, payload: { response: mockResponse } });
expect(result.dialogs[0]).toBe('test dialogs');
expect(result.lgFiles[0]).toBe('test lgFiles');
expect(result.schemas).toBe('test schemas');
});
it('test createDialogSuccess reducer', () => {
const result = reducer({}, { type: ActionTypes.CREATE_DIALOG_SUCCESS, payload: { response: mockResponse } });
expect(result.dialogs[0]).toBe('test dialogs');
});
it('test updateLgTemplate reducer', () => {
const result = reducer(
{ lgFiles: [{ id: 'common.lg', content: 'test lgFiles' }] },
{
type: ActionTypes.UPDATE_LG_SUCCESS,
payload: {
id: 'common.lg',
content: ` # bfdactivity-003038
- You said '\${turn.activity.text}'`,
},
}
);
expect(result.lgFiles[0].templates.length).toBe(1);
});

it('test getStorageFileSuccess reducer', () => {
const mockStorageFile = {
data: {
Expand Down
23 changes: 7 additions & 16 deletions Composer/packages/client/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,15 @@ export enum ActionTypes {
GET_TEMPLATE_PROJECTS_FAILURE = 'GET_TEMPLATE_PROJECTS_FAILURE',
CREATE_DIALOG_BEGIN = 'CREATE_DIALOG_BEGIN',
CREATE_DIALOG_CANCEL = 'CREATE_DIALOG_CANCEL',
CREATE_DIALOG_SUCCESS = 'CREATE_DIALOG_SUCCESS',
CREATE_DIALOG = 'CREATE_DIALOG',
UPDATE_DIALOG = 'UPDATE_DIALOG',
REMOVE_DIALOG = 'REMOVE_DIALOG',
UPDATE_LG_SUCCESS = 'UPDATE_LG_SUCCESS',
UPDATE_LG_FAILURE = 'UPDATE_LG_FAILURE',
CREATE_LG_SUCCCESS = 'CREATE_LG_SUCCCESS',
CREATE_LG_FAILURE = 'CREATE_LG_FAILURE',
REMOVE_LG_SUCCCESS = 'REMOVE_LG_SUCCCESS',
REMOVE_LG_FAILURE = 'REMOVE_LG_FAILURE',
UPDATE_LU_SUCCESS = 'UPDATE_LU_SUCCESS',
UPDATE_LU_FAILURE = 'UPDATE_LU_FAILURE',
CREATE_LU_SUCCCESS = 'CREATE_LU_SUCCCESS',
CREATE_LU_FAILURE = 'CREATE_LU_FAILURE',
REMOVE_LU_SUCCCESS = 'REMOVE_LU_SUCCCESS',
REMOVE_LU_FAILURE = 'REMOVE_LU_FAILURE',
UPDATE_LG = 'UPDATE_LG',
CREATE_LG = 'CREATE_LG',
REMOVE_LG = 'REMOVE_LG',
UPDATE_LU = 'UPDATE_LU',
CREATE_LU = 'CREATE_LU',
REMOVE_LU = 'REMOVE_LU',
PUBLISH_LU_SUCCCESS = 'PUBLISH_LU_SUCCCESS',
SAVE_TEMPLATE_ID = 'SAVE_TEMPLATE_ID',
GET_STORAGE_SUCCESS = 'GET_STORAGE_SUCCESS',
Expand All @@ -84,12 +78,9 @@ export enum ActionTypes {
ONBOARDING_ADD_COACH_MARK_REF = 'ONBOARDING_ADD_COACH_MARK_REF',
ONBOARDING_SET_COMPLETE = 'ONBOARDING_SET_COMPLETE',
GET_PUBLISH_TYPES_SUCCESS = 'GET_PUBLISH_TYPES_SUCCESS',
// GET_PUBLISH_TYPES_FAILURE = 'GET_PUBLISH_TYPES_FAILURE',
PUBLISH_SUCCESS = 'PUBLISH_SUCCESS',
PUBLISH_FAILED = 'PUBLISH_FAILED',
GET_PUBLISH_STATUS = 'GET_PUBLISH_STATUS',
// GET_PUBLISH_STATUS_FAILED = 'GET_PUBLISH_STATUS_FAILED',
UPDATE_TIMESTAMP = 'UPDATE_TIMESTAMP',
}

export const Tips = {
Expand Down
2 changes: 1 addition & 1 deletion Composer/packages/client/src/pages/design/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ function DesignPage(props) {
dialogId={dialogId}
selected={selected}
onSelect={handleSelect}
onAdd={() => actions.createDialogBegin(onCreateDialogComplete)}
onAdd={() => actions.createDialogBegin({}, onCreateDialogComplete)}
onDeleteDialog={handleDeleteDialog}
onDeleteTrigger={handleDeleteTrigger}
openNewTriggerModal={openNewTriggerModal}
Expand Down
213 changes: 95 additions & 118 deletions Composer/packages/client/src/store/action/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,159 +1,136 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import clonedeep from 'lodash/cloneDeep';
import reject from 'lodash/reject';
import { DialogInfo } from '@bfc/shared';
import debounce from 'lodash/debounce';
import { autofixReferInDialog } from '@bfc/indexers';

import { ActionCreator, State } from '../types';
import { undoable, Pick } from '../middlewares/undo';
import luFileStatusStorage from '../../utils/luFileStatusStorage';
import { undoable } from '../middlewares/undo';
import { getBaseName } from '../../utils/fileUtil';

import { removeLgFile, createLgFile } from './lg';
import { ActionTypes } from './../../constants/index';
import { navTo } from './navigation';
import { Store } from './../types';
import httpClient from './../../utils/httpUtil';
import { setError } from './error';
import { createLuFile, removeLuFile } from './lu';

interface FileResource {
id: string;
content: string;
}

export const removeDialogBase: ActionCreator = async (store, id) => {
store.dispatch({
type: ActionTypes.REMOVE_DIALOG,
payload: { id },
});
};

const pickDialog: Pick = (state: State, args: any[], isStackEmpty) => {
const id = args[0];
const dialog = state.dialogs.find(dialog => dialog.id === id);
let dialogs = clonedeep(state.dialogs);
if (!isStackEmpty) {
dialogs = reject(dialogs, ['id', id]);
export const createDialogBase: ActionCreator = async (store, { id, content }) => {
const fixedContent = autofixReferInDialog(id, content);
const onCreateDialogComplete = store.getState().onCreateDialogComplete;
if (typeof onCreateDialogComplete === 'function') {
setTimeout(() => onCreateDialogComplete(id));
}
return [{ id, content: dialog ? dialog.content : {}, dialogs }];
store.dispatch({
type: ActionTypes.CREATE_DIALOG,
payload: { id, content: fixedContent },
});
};

const getDiff = (dialogs1: DialogInfo[], dialogs2: DialogInfo[]) => {
const temp = [];
dialogs1.forEach(dialog => {
temp[dialog.id] = true;
export const updateDialogBase: ActionCreator = async (store, { id, content }) => {
store.dispatch({
type: ActionTypes.UPDATE_DIALOG,
payload: { id, content },
});
for (const dialog of dialogs2) {
if (!temp[dialog.id]) {
return { id: dialog.id, content: dialog.content };
}
}
};

export const removeDialogBase: ActionCreator = async (store, id) => {
try {
const projectId = store.getState().projectId;
const response = await httpClient.delete(`/projects/${projectId}/dialogs/${id}`);
luFileStatusStorage.removeFile(store.getState().botName, id);
store.dispatch({
type: ActionTypes.REMOVE_DIALOG,
payload: { response },
});
navTo(store, 'Main');
} catch (err) {
setError(store, {
message: err.response && err.response.data.message ? err.response.data.message : err,
summary: 'DELETE DIALOG ERROR',
});
}
export const removeAllRelatedFiles: ActionCreator = async (store, id) => {
removeDialogBase(store, id);
//remove dialog should remove all locales lu and lg files
const { luFiles, lgFiles } = store.getState();
const luIds = luFiles.filter(file => getBaseName(file.id) === id);
const lgIds = lgFiles.filter(file => getBaseName(file.id) === id);
luIds.forEach(({ id }) => removeLuFile(store, id));
lgIds.forEach(({ id }) => removeLgFile(store, id));

navTo(store, 'Main');
};

export const createDialogBase: ActionCreator = async (store, { id, content }) => {
try {
const projectId = store.getState().projectId;
const response = await httpClient.post(`/projects/${projectId}/dialogs`, { id, content, projectId });
const onCreateDialogComplete = store.getState().onCreateDialogComplete;
if (typeof onCreateDialogComplete === 'function') {
setTimeout(() => onCreateDialogComplete(id));
}
store.dispatch({
type: ActionTypes.CREATE_DIALOG_SUCCESS,
payload: {
response,
},
});
} catch (err) {
setError(store, {
message: err.response && err.response.data.message ? err.response.data.message : err,
summary: 'CREATE DIALOG ERROR',
});
export const createAllRelatedFiles: ActionCreator = async (
store,
dialog: FileResource,
lus?: FileResource[],
lgs?: FileResource[]
) => {
createDialogBase(store, dialog);
const locale = store.getState().locale;
//createDialog function need to create lg, lu and dialog
if (!lus) {
createLuFile(store, { id: `${dialog.id}.${locale}`, content: '' });
} else {
lus.forEach(lu => createLuFile(store, { id: lu.id, content: lu.content }));
}

if (!lgs) {
createLgFile(store, { id: `${dialog.id}.${locale}`, content: '' });
} else {
lgs.forEach(lg => createLgFile(store, { id: lg.id, content: lg.content }));
}
};

export const removeDialog = undoable(
removeDialogBase,
pickDialog,
async (store: Store, { dialogs }) => {
const target = getDiff(store.getState().dialogs, dialogs);
if (target) {
await createDialogBase(store, { ...target });
}
removeAllRelatedFiles,
(state: State, args: any[], isStackEmpty) => {
if (isStackEmpty) return [];
const id = args[0];
const dialog = state.dialogs.find(dialog => dialog.id === id);
const lus = state.luFiles
.filter(file => getBaseName(file.id) === id)
.map(file => {
return { id: file.id, content: file.content };
});
const lgs = state.lgFiles
.filter(file => getBaseName(file.id) === id)
.map(file => {
return { id: file.id, content: file.content };
});
return [{ id, content: dialog?.content || '' }, lus, lgs];
},
async (store: Store, from, to) => {
await createAllRelatedFiles(store, ...from);
},
(store, { id }) => removeDialogBase(store, id)
(store, from, to) => removeAllRelatedFiles(store, to[0].id)
);

export const createDialog = undoable(
createDialogBase,
pickDialog,
async (store: Store, { dialogs }) => {
const target = getDiff(dialogs, store.getState().dialogs);
if (target) {
await removeDialogBase(store, target.id);
}
createAllRelatedFiles,
(state: State, args: any[], isStackEmpty) => {
if (isStackEmpty) return [];
const id = args[0].id;
const content = args[0].content;
const locale = state.locale;
const lus = [{ id: `${id}.${locale}`, content: '' }];
const lgs = [{ id: `${id}.${locale}`, content: '' }];
return [{ id, content }, lus, lgs];
},
(store: Store, from, to) => {
removeAllRelatedFiles(store, from[0].id);
},
(store, { id, content }) => createDialogBase(store, { id, content })
(store, from, to) => createAllRelatedFiles(store, ...to)
);

export const debouncedUpdateDialog = debounce(async (store, id, projectId, content, lastModified) => {
try {
const response = await httpClient.put(`/projects/${projectId}/dialogs/${id}`, {
id,
projectId,
content,
lastModified,
});
store.dispatch({
type: ActionTypes.UPDATE_TIMESTAMP,
payload: {
id: id,
type: 'dialog',
lastModified: response.data.lastModified,
},
});
} catch (err) {
setError(store, {
status: err.response.status,
message: err.response && err.response.data.message ? err.response.data.message : err,
summary: 'UPDATE DIALOG ERROR',
});
}
}, 500);

export const updateDialogBase: ActionCreator = (store, { id, content }) => {
const state = store.getState();
const projectId = state.projectId;
const dialog = state.dialogs.find(dialog => dialog.id === id);
if (dialog) {
store.dispatch({
type: ActionTypes.UPDATE_DIALOG,
payload: { id, projectId, content, lastModified: dialog.lastModified },
});
debouncedUpdateDialog(store, id, projectId, content, dialog.lastModified);
}
};

export const updateDialog: ActionCreator = undoable(
updateDialogBase,
(state: State, args: any[], isEmpty) => {
if (isEmpty) {
const id = state.designPageLocation.dialogId;
const dialog = state.dialogs.find(dialog => dialog.id === id);
const projectId = state.projectId;
return [{ id, projectId, content: dialog ? dialog.content : {} }];
return [{ id, content: dialog ? dialog.content : {} }];
} else {
return args;
}
},
updateDialogBase,
updateDialogBase
(store: Store, from, to) => updateDialogBase(store, ...to),
(store: Store, from, to) => updateDialogBase(store, ...to)
);

export const createDialogBegin: ActionCreator = ({ dispatch }, { actions }, onComplete) => {
Expand Down
Loading