Skip to content

Commit

Permalink
[BOT] feature/server-side-bot (#15701)
Browse files Browse the repository at this point in the history
* [BOT] [Experemental]Maryia/bot-1230/feat: ui server-side bot MVP (#12985)

* feat: add new Bot UI

* feat: form for server side bot

* feat: implement new UI with components

* feat: add interface, type, etc for server side bot

* chore: organize/sort files in a new way as in the master branch(2)

* feat: ui bot list

* refactor: move methods to the class, add ts

* chore: fix sonar cloud code smell

* feat: add api calls start_bot, stop_bot

* feat: add @deriv/hooks to package.json

* feat: notify bot

* fix: comment setItem()

* feat: apply handle submit for qs server side form, fix bugs, apply ts

* feat: handle notification messages

* refactor: naming

* chore: resolve conflicts (part2)

* chore: resolve conflicts (part3)

* chore: resolve conflicts (part4)

* chore: resolve conflicts (part5)

* chore: revert a file

* chore: revert a file

* chore: revert a file

* [BOT] [Server Side Bot]maryia/bot 1913/Test link bug fixing and improvements (#15719)

* fix: integrate a toggle button which will switch to stop if the bot is running

* feat: make available bot subscription after reloading the page

* feat: 🔥 fixing server side bot bugs and ui issues

* feat: 🔥 server side bot improvements after api update

* fix: 🔥 style fix

* fix: 🔥 fixing style issues

* Feature/server side bot (#16067)

* feat: 🔥 fixing server side bot bugs and ui issues

* feat: 🔥 server side bot improvements after api update

* fix: 🔥 style fix

* fix: 🔥 fixing style issues

* feat: removed dvh and added exceptions for temporary consoles

* feat: 🔥 added mobile/desktop UI for server side bot

* feat: server side bot adding functionalities

* feat: 🔥 server bot journal implementation

* fix: login and server bot (#16259)

* feat: 🔥 working on journal/summary

* chore: name validation and stop bot issue (#16273)

* feat: 🚧 added performance, made minor improvements

* feat: 🚧 fixed summary and made mobile improvements

* feat: 🚧 tracking bot subscriptions

* refactor: 🚧 fixed sonar cloud warnings

* feat: 🚧 added download and reset feature

* fix: server side bot test cases (#16289)

* feat: ✨ added country check

* feat: ✨ Cleanup and updated content messages

* chore: 🧪 improved test coverage variables

* refactor: remove extra line breaks

* refactor: replace small localize with big Localize

* refactor: replace small localize with big Localize

* feat: 🚧 nit picks and file renaming

* chore: 🔥 indentation and eslint suggestions

* chore: 🔥 cleanup consoles

---------

Co-authored-by: maryia-matskevich-deriv <103181650+maryia-matskevich-deriv@users.noreply.github.com>
Co-authored-by: rupato-deriv <97010868+rupato-deriv@users.noreply.github.com>
Co-authored-by: Shafin Al Karim <129021108+shafin-deriv@users.noreply.github.com>
Co-authored-by: Farabi <102643568+farabi-deriv@users.noreply.github.com>
Co-authored-by: Maryia <103177211+maryia-deriv@users.noreply.github.com>
  • Loading branch information
6 people authored Aug 1, 2024
1 parent da24792 commit 9991e86
Show file tree
Hide file tree
Showing 67 changed files with 6,204 additions and 5 deletions.
1 change: 1 addition & 0 deletions packages/bot-web-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"@deriv/api-types": "1.0.172",
"@deriv/bot-skeleton": "^1.0.0",
"@deriv/components": "^1.0.0",
"@deriv/hooks": "^1.0.0",
"@deriv/deriv-charts": "^2.2.0",
"@deriv/shared": "^1.0.0",
"@deriv/stores": "^1.0.0",
Expand Down
3 changes: 2 additions & 1 deletion packages/bot-web-ui/src/constants/bot-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export const DBOT_TABS: TDashboardTabIndex = Object.freeze({
BOT_BUILDER: 1,
CHART: 2,
TUTORIAL: 3,
SERVER_BOT: 4,
});

export const MAX_STRATEGIES = 10;

export const TAB_IDS = ['id-dbot-dashboard', 'id-bot-builder', 'id-charts', 'id-tutorials'];
export const TAB_IDS = ['id-dbot-dashboard', 'id-bot-builder', 'id-charts', 'id-tutorials', 'id-server-bot'];

export const DEBOUNCE_INTERVAL_TIME = 500;
14 changes: 14 additions & 0 deletions packages/bot-web-ui/src/pages/main/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -686,3 +686,17 @@
width: 2.5rem !important;
padding: 0.2rem 0;
}

.beta-server-bot {
color: var(--brand-red-coral);
margin-left: 8px;
padding: 4px 8px;
height: 22px;
border-radius: 16px;
font-size: 1.2rem;
border: 2px solid var(--brand-red-coral);

display: flex;
align-items: center;
justify-content: center;
}
35 changes: 31 additions & 4 deletions packages/bot-web-ui/src/pages/main/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dbot from '@deriv/bot-skeleton/src/scratch/dbot';
import { api_base } from '@deriv/bot-skeleton/src/services/api/api-base';
import { isDbotRTL } from '@deriv/bot-skeleton/src/utils/workspace';
import { Dialog, Tabs } from '@deriv/components';
import { useFeatureFlags } from '@deriv/hooks';
import { observer, useStore } from '@deriv/stores';
import { Localize, localize } from '@deriv/translations';
import TradingViewModal from 'Components/trading-view-chart/trading-view-modal';
Expand All @@ -15,6 +16,7 @@ import Chart from '../chart';
import ChartModal from '../chart/chart-modal';
import Dashboard from '../dashboard';
import RunStrategy from '../dashboard/run-strategy';
import ServerSideBot from '../server-side-bot';
import Tutorial from '../tutorials';
import { tour_list } from '../tutorials/dbot-tours/utils';

Expand All @@ -38,10 +40,17 @@ const AppWrapper = observer(() => {
const { clear } = summary_card;
const { DASHBOARD, BOT_BUILDER } = DBOT_TABS;
const init_render = React.useRef(true);
const { ui } = useStore();
const { ui, client } = useStore();
const { is_next_server_bot_enabled } = useFeatureFlags();
const { url_hashed_values, is_desktop } = ui;
const { account_settings } = client;

const hash = ['dashboard', 'bot_builder', 'chart', 'tutorial'];

// SERVER BOT VISIBILITY SET HERE //
const should_show_server_bot = is_next_server_bot_enabled && account_settings?.country_code?.toLowerCase() === 'aq';
if (should_show_server_bot) hash.push('server_bot');

let tab_value: number | string = active_tab;
const GetHashedValue = (tab: number) => {
tab_value = url_hashed_values?.split('#')[1];
Expand Down Expand Up @@ -180,20 +189,38 @@ const AppWrapper = observer(() => {
<Tutorial handleTabChange={handleTabChange} />
</div>
</div>
{should_show_server_bot ? (
<div
icon='IcServerBot'
label={
<Localize
i18n_default_text='Server Bot <0>Beta</0'
components={[<span key={0} className='beta-server-bot' />]}
/>
}
id='id-server-bot'
>
<ServerSideBot />
</div>
) : null}
</Tabs>
</div>
</div>
{is_desktop ? (
<>
<div className='main__run-strategy-wrapper'>
<RunStrategy />
<RunPanel />
{active_tab !== 4 && (
<>
<RunStrategy />
<RunPanel />
</>
)}
</div>
<ChartModal />
<TradingViewModal />
</>
) : (
!is_open && <RunPanel />
!is_open && active_tab !== 4 && <RunPanel />
)}
<Dialog
cancel_button_text={cancel_button_text || localize('Cancel')}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import React from 'react';
import { mockStore, StoreProvider } from '@deriv/stores';
import { render, waitFor } from '@testing-library/react';
import { mock_ws } from 'Utils/mock';
import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore';
import AddBot from '../add-bot';

jest.mock('@deriv/bot-skeleton/src/scratch/blockly', () => jest.fn());
jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => jest.fn());
jest.mock('@deriv/bot-skeleton/src/scratch/hooks/block_svg', () => jest.fn());

jest.mock('@deriv/components', () => ({
...jest.requireActual('@deriv/components'),
Modal: ({ children, is_open }: { children: JSX.Element; is_open: boolean }) => is_open && <div>{children}</div>,
}));

jest.mock('@deriv/bot-skeleton', () => ({
...jest.requireActual('@deriv/bot-skeleton'),
ApiHelpers: {
instance: {
contracts_for: {
getDurations: () => [
{
display: 'Ticks',
unit: 't',
min: 1,
max: 10,
},
{
display: 'sample',
unit: 's',
min: 10,
max: 20,
},
],
getMarketBySymbol: jest.fn(),
getSubmarketBySymbol: jest.fn(),
getTradeTypeCategoryByTradeType: jest.fn(),
getTradeTypesForQuickStrategy: () => [
{
text: 'Rise/Fall',
value: 'callput',
group: 'Up/Down',
icon: ['CALL', 'PUT'],
},
{
text: 'Rise Equals/Fall Equals',
value: 'callputequal',
group: 'Up/Down',
icon: ['CALLE', 'PUTE'],
},
],
},
active_symbols: {
getSymbolsForBot: () => [
{
text: 'AUD Basket',
value: 'WLDAUD',
group: 'Forex Basket',
},
{
text: 'Bear Market Index',
value: 'RDBULL',
group: 'Daily Reset Indices',
},
],
},
},
},
config: {
...jest.requireActual('@deriv/bot-skeleton').config,
QUICK_STRATEGY: {
DISABLED: {
SYMBOLS: ['1HZ150V', '1HZ250V'],
SUBMARKETS: ['crash_index', 'non_stable_coin'],
},
DEFAULT: {
symbol: '1HZ100V',
tradetype: 'callput',
durationtype: 't',
size: 2,
unit: 1,
},
},
},
}));

jest.mock('../../../../xml/martingale.xml', () => '');

window.Blockly = {
Xml: {
textToDom: jest.fn(),
domToText: jest.fn(),
},
utils: {
xml: { textToDom: jest.fn() },
},
};

jest.mock('../config', () => ({
...jest.requireActual('../config'),
STRATEGIES: {
MARTINGALE: {
name: 'martingale',
label: 'martingale',
description: 'martingale',
fields: [
[
{
type: 'symbol',
name: 'symbol',
},
{
type: 'tradetype',
name: 'tradetype',
dependencies: ['symbol'],
},
{
type: 'durationtype',
name: 'durationtype',
dependencies: ['symbol', 'tradetype'],
attached: true,
},
{
type: 'number',
name: 'duration',
validation: ['required', 'number', 'floor', 'min', 'max'],
},
{
type: 'duration',
name: 'duration',
validation: ['required', 'number', 'floor', 'min', 'max'],
},
{
type: 'number',
name: 'size',
validation: ['required'],
},
{
type: 'number',
name: 'profit',
validation: ['number', 'ceil'],
},
{
type: 'number',
name: 'loss',
validation: [
'number',
{
type: 'max',
value: 10,
getMessage: () => 'some message',
},
],
},
{
type: 'label',
label: 'Duration',
description: 'The trade length of your purchased contract.',
hide: ['desktop'],
},
{
type: 'number',
name: 'last_digit_prediction',
validation: ['number', 'integer'],
should_have: [{ key: 'symbol', value: 'WLDAUD' }],
hide_without_should_have: true,
},
{
type: 'number',
name: 'max_stake',
validation: ['number', 'integer'],
should_have: [{ key: 'symbol', value: '', multiple: ['WLDAUD'] }],
hide_without_should_have: true,
},
],
[],
],
},
},
}));

describe('<QuickStrategy />', () => {
let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element;
let mock_store = mockStore({
ui: {
is_mobile: true,
},
});
const mock_dbot_store = mockDBotStore(mock_store, mock_ws);

beforeEach(() => {
mock_dbot_store?.quick_strategy?.setValue('durationtype', 't');
mock_dbot_store?.quick_strategy?.setSelectedStrategy('MARTINGALE');
mock_dbot_store?.quick_strategy?.setFormVisibility(true);
wrapper = ({ children }: { children: JSX.Element }) => (
<StoreProvider store={mock_store}>
<DBotStoreProvider ws={mock_ws} mock={mock_dbot_store}>
{children}
</DBotStoreProvider>
</StoreProvider>
);
});

it('should render QuickStrategy', async () => {
const mock_setVisibility = jest.fn();
const is_open = false;
const { container } = render(<AddBot setFormVisibility={mock_setVisibility} is_open={is_open} />, {
wrapper,
});

await waitFor(() => {
expect(container).toBeInTheDocument();
});
});

it('It should render desktop', () => {
mock_store = mockStore({
ui: {
is_mobile: false,
},
});
const mock_setVisibility = jest.fn();
const is_open = false;
render(<AddBot setFormVisibility={mock_setVisibility} is_open={is_open} />, {
wrapper,
});
});
});
Loading

0 comments on commit 9991e86

Please sign in to comment.