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 all 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
40 changes: 40 additions & 0 deletions Composer/packages/client/__tests__/store/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { applyMiddleware } from './../../src/store';
import { Store, State } from './../../src/store/types';
import { ActionTypes } from './../../src/constants';

describe('applyMiddleware', () => {
it('warns when dispatching during middleware setup', () => {
const store: Store = {
dispatch: jest.fn(),
getState: () => {
return {} as State;
},
};
const mockFunction1 = jest.fn();
const mockFunction2 = jest.fn();

const middleWare1 = (store: Store) => next => {
return () => {
mockFunction1();
return next();
};
};

const middleWare2 = (store: Store) => next => {
return () => {
mockFunction2();
return next();
};
};
const dispatch = applyMiddleware(store, middleWare1, middleWare2);
dispatch({
type: ActionTypes.UPDATE_BOTSTATUS,
payload: { a: 'a' },
});
expect(mockFunction1).toBeCalledTimes(1);
expect(mockFunction2).toBeCalledTimes(1);
expect(store.dispatch).toBeCalledTimes(1);
});
});
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
@@ -0,0 +1,170 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { DialogInfo, LgFile, LuFile } from '@bfc/shared';

import { ActionTypes } from './../../../src/constants';
import filePersistence from './../../../src/store/persistence/FilePersistence';
import { State } from './../../../src/store/types';

jest.mock('axios', () => {
return {
create: jest.fn(() => {
return {
put: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn((url, data) => Promise.resolve({ data })),
delete: jest.fn(() => Promise.resolve({ data: {} })),
};
}),
};
});

const files = [
{
name: 'a.dialog',
content: '',
lastModified: 'Tue Mar 31 2020 23:08:15 GMT+0800 (GMT+08:00)',
path: 'C:/a.dialog',
relativePath: 'a.dialog',
},
{
name: 'a.en-us.lg',
content: '',
lastModified: 'Tue Mar 31 2020 23:08:15 GMT+0800 (GMT+08:00)',
path: 'C:/a.dialog',
relativePath: 'a.en-us.lg',
},
{
name: 'a.en-us.lu',
content: '',
lastModified: 'Tue Mar 31 2020 23:08:15 GMT+0800 (GMT+08:00)',
path: 'C:/a.en-us.lu',
relativePath: 'a.en-us.lu',
},
];

describe('test persistence layer', () => {
it('test attach file', () => {
expect(Object.keys(filePersistence.files).length).toBe(0);
expect(filePersistence.projectId).toBe('');
filePersistence.projectId = 'projectId';
files.forEach(file => filePersistence.attach(file.name, file));
expect(Object.keys(filePersistence.files).length).toBe(3);
});

it('test notify update', async () => {
const state1 = {
dialogs: [{ id: 'a', content: {} }] as DialogInfo[],
lgFiles: [{ id: 'a.en-us', content: '' }] as LgFile[],
luFiles: [{ id: 'a.en-us', content: '' }] as LuFile[],
} as State;

const state2 = {
dialogs: [{ id: 'a', content: { a: 'a' } }] as DialogInfo[],
lgFiles: [{ id: 'a.en-us', content: 'a' }] as LgFile[],
luFiles: [{ id: 'a.en-us', content: 'a' }] as LuFile[],
} as State;

await filePersistence.notify(state1, state2, { type: ActionTypes.UPDATE_DIALOG, payload: { id: 'a' } });
await filePersistence.notify(state1, state2, { type: ActionTypes.UPDATE_LG, payload: { id: 'a.en-us' } });
await filePersistence.notify(state1, state2, { type: ActionTypes.UPDATE_LU, payload: { id: 'a.en-us' } });
await new Promise(res =>
setTimeout(() => {
const dialog = filePersistence.files['a.dialog'].file;
const lg = filePersistence.files['a.en-us.lg'].file;
const lu = filePersistence.files['a.en-us.lu'].file;
expect(dialog).toBeDefined();
if (dialog) {
expect(JSON.parse(dialog.content).a).toBe('a');
}
expect(lg).toBeDefined();
if (lg) {
expect(lg.content).toBe('a');
}
expect(lu).toBeDefined();
if (lu) {
expect(lu.content).toBe('a');
}
res();
}, 601)
);
});

it('test notify create', async () => {
const state1 = {
dialogs: [{ id: 'a', content: { a: 'a' } }] as DialogInfo[],
lgFiles: [{ id: 'a.en-us', content: 'a' }] as LgFile[],
luFiles: [{ id: 'a.en-us', content: 'a' }] as LuFile[],
} as State;

const state2 = {
dialogs: [
{ id: 'a', content: { a: 'a' } },
{ id: 'b', content: { b: 'b' } },
] as DialogInfo[],
lgFiles: [
{ id: 'a.en-us', content: 'a' },
{ id: 'b.en-us', content: 'b' },
] as LgFile[],
luFiles: [
{ id: 'a.en-us', content: 'a' },
{ id: 'b.en-us', content: 'b' },
] as LuFile[],
} as State;

await filePersistence.notify(state1, state2, { type: ActionTypes.CREATE_DIALOG, payload: { id: 'b' } });
const dialog = filePersistence.files['b.dialog'].file;
const lg = filePersistence.files['b.en-us.lg'].file;
const lu = filePersistence.files['b.en-us.lu'].file;
expect(dialog).toBeDefined();
if (dialog) {
expect(JSON.parse(dialog.content).b).toBe('b');
}
expect(lg).toBeDefined();
if (lg) {
expect(lg.content).toBe('b');
}
expect(lu).toBeDefined();
if (lu) {
expect(lu.content).toBe('b');
}
});

it('test notify remove', async () => {
const state1 = {
dialogs: [
{ id: 'a', content: { a: 'a' } },
{ id: 'b', content: { b: 'b' } },
] as DialogInfo[],
lgFiles: [
{ id: 'a.en-us', content: 'a' },
{ id: 'b.en-us', content: 'b' },
] as LgFile[],
luFiles: [
{ id: 'a.en-us', content: 'a' },
{ id: 'b.en-us', content: 'b' },
] as LuFile[],
} as State;

const state2 = {
dialogs: [{ id: 'a', content: { a: 'a' } }] as DialogInfo[],
lgFiles: [{ id: 'a.en-us', content: 'a' }] as LgFile[],
luFiles: [{ id: 'a.en-us', content: 'a' }] as LuFile[],
} as State;

await filePersistence.notify(state1, state2, { type: ActionTypes.REMOVE_DIALOG, payload: { id: 'b' } });
const dialog = filePersistence.files['b.dialog'];
const lg = filePersistence.files['b.en-us.lg'];
const lu = filePersistence.files['b.en-us.lu'];
expect(dialog).toBeUndefined();
expect(lg).toBeUndefined();
expect(lu).toBeUndefined();
});
it('test detach', async () => {
filePersistence.detach('a.dialog');
expect(filePersistence.files['a.dialog']).toBeUndefined();
});
it('test clear', async () => {
filePersistence.clear();
expect(Object.keys(filePersistence.files).length).toBe(0);
});
});
93 changes: 65 additions & 28 deletions Composer/packages/client/__tests__/store/reducer/reducer.test.js
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 All @@ -63,4 +36,68 @@ describe('test all reducer handlers', () => {
expect(result.focusedStorageFolder).toEqual(expect.objectContaining({ children: expect.any(Array) }));
expect(result.focusedStorageFolder.children).toHaveLength(2);
});

it('remove lg file', () => {
const result = reducer(
{ lgFiles: [{ id: '1' }, { id: '2' }] },
{ type: ActionTypes.REMOVE_LG, payload: { id: '1' } }
);
expect(result.lgFiles.length).toBe(1);
expect(result.lgFiles[0].id).toBe('2');
});

it('create lg file', () => {
const result = reducer(
{ lgFiles: [{ id: '1' }, { id: '2' }], locale: 'en-us' },
{ type: ActionTypes.CREATE_LG, payload: { id: '3', content: '' } }
);
expect(result.lgFiles.length).toBe(3);
expect(result.lgFiles[2].id).toBe('3.en-us');
});

it('update lg file', () => {
const result = reducer(
{ lgFiles: [{ id: '1', content: 'old' }, { id: '2' }] },
{ type: ActionTypes.UPDATE_LG, payload: { id: '1', content: 'new' } }
);
expect(result.lgFiles.length).toBe(2);
expect(result.lgFiles[0].content).toBe('new');
});

it('remove dialog file', () => {
const result = reducer(
{ dialogs: [{ id: '1' }, { id: '2' }], lgFiles: [{ id: '1' }], luFiles: [{ id: '1' }] },
{ type: ActionTypes.REMOVE_DIALOG, payload: { id: '1' } }
);
expect(result.dialogs.length).toBe(1);
expect(result.dialogs[0].id).toBe('2');
expect(result.luFiles.length).toBe(0);
expect(result.lgFiles.length).toBe(0);
});

it('create dialog file', () => {
const result = reducer(
{
dialogs: [{ id: '1' }, { id: '2' }],
locale: 'en-us',
lgFiles: [],
luFiles: [],
schemas: { sdk: { content: {} } },
},
{ type: ActionTypes.CREATE_DIALOG, payload: { id: '3', content: '' } }
);
expect(result.dialogs.length).toBe(3);
expect(result.dialogs[2].id).toBe('3');
expect(result.luFiles.length).toBe(1);
expect(result.lgFiles.length).toBe(1);
});

it('update dialog file', () => {
const result = reducer(
{ dialogs: [{ id: '1', content: 'old' }, { id: '2' }], schemas: { sdk: { content: {} } } },
{ type: ActionTypes.UPDATE_DIALOG, payload: { id: '1', content: 'new' } }
);
expect(result.dialogs.length).toBe(2);
expect(result.dialogs[0].content).toBe('new');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import luFileStatusStorage from '../../src/utils/luFileStatusStorage';

const fileIds = ['1', '2', '3', '4'];
const botName = 'test_status';

afterAll(() => luFileStatusStorage.removeAllStatuses(botName));

describe('luFileStatusStorage', () => {
it('check file status', () => {
luFileStatusStorage.checkFileStatus(botName, fileIds);
expect(Object.keys(luFileStatusStorage.get(botName)).length).toEqual(4);
});

it('the statuses after publishing', () => {
luFileStatusStorage.publishAll(botName);
const result = luFileStatusStorage.get(botName);
Object.keys(result).forEach(id => {
expect(result[id]).toBeTruthy();
});
});

it('update one luis file', () => {
luFileStatusStorage.updateFileStatus(botName, fileIds[0]);
const result = luFileStatusStorage.get(botName);
expect(result[fileIds[0]]).toBeFalsy();
expect(result[fileIds[1]]).toBeTruthy();
expect(result[fileIds[2]]).toBeTruthy();
expect(result[fileIds[3]]).toBeTruthy();
});

it('remove one luis file', () => {
luFileStatusStorage.removeFileStatus(botName, fileIds[0]);
const result = luFileStatusStorage.get(botName);
expect(result[fileIds[0]]).toBeUndefined;
expect(result[fileIds[1]]).toBeTruthy();
expect(result[fileIds[2]]).toBeTruthy();
expect(result[fileIds[3]]).toBeTruthy();
});

it('remove all statuses', () => {
luFileStatusStorage.removeAllStatuses(botName);
const result = luFileStatusStorage.get(botName);
expect(result).toBeUndefined;
});
});
Loading