From d5966a12397e479ad9f4a4f92101e2a651c9153b Mon Sep 17 00:00:00 2001 From: Krzysztof Jamroz Date: Wed, 3 Jun 2020 16:36:33 +0200 Subject: [PATCH] feat(jsbattle-webpage): add league opponents to sandbox mode --- .../app/services/ApiGateway.service.js | 2 + .../app/services/League.service.js | 45 +++++++++++++++++-- .../jsbattle-webpage/features/schema.feature | 1 + .../src/actions/sandboxAction.js | 39 +++++++++++++--- .../src/components/LeagueJoin.js | 15 ++++++- .../components/LiveCodeSandboxSettingsTab.js | 44 +++++++++++++++--- .../test/LiveCodeSandboxSettingsTab.spec.js | 27 ++++++----- .../src/containers/SandboxScreen.js | 12 ++--- 8 files changed, 155 insertions(+), 30 deletions(-) diff --git a/packages/jsbattle-server/app/services/ApiGateway.service.js b/packages/jsbattle-server/app/services/ApiGateway.service.js index 6537604a..7a65fabf 100644 --- a/packages/jsbattle-server/app/services/ApiGateway.service.js +++ b/packages/jsbattle-server/app/services/ApiGateway.service.js @@ -73,6 +73,8 @@ class ApiGatewayService extends Service { "GET league/": "league.getLeagueSummary", "GET league/replay/:id": "battleStore.get", "GET league/submission": "league.getUserSubmission", + "GET league/ranktable": "league.getUserRankTable", + "GET league/scripts/:id": "league.getScript", "PATCH league/submission": "league.joinLeague", "DELETE league/submission": "league.leaveLeague", }, diff --git a/packages/jsbattle-server/app/services/League.service.js b/packages/jsbattle-server/app/services/League.service.js index e60ddfcf..4b7eab01 100644 --- a/packages/jsbattle-server/app/services/League.service.js +++ b/packages/jsbattle-server/app/services/League.service.js @@ -59,9 +59,11 @@ class LeagueService extends Service { seedLeague: this.seedLeague, getUserSubmission: this.getUserSubmission, getHistory: this.getHistory, + getScript: this.getScript, joinLeague: this.joinLeague, leaveLeague: this.leaveLeague, getLeagueSummary: this.getLeagueSummary, + getUserRankTable: this.getUserRankTable, updateRank: this.updateRank }, hooks: { @@ -108,6 +110,31 @@ class LeagueService extends Service { }); } + + async getScript(ctx) { + const userId = ctx.meta.user ? ctx.meta.user.id : null; + if(!userId) { + throw new ValidationError('Not Authorized!', 401); + } + + const scriptId = ctx.params.id + let response = await ctx.call('league.get', { + id: scriptId, + fields: [ + "id", + "ownerName", + "scriptName", + "code" + ] + }); + + return { + id: response.id, + scriptName: response.ownerName + '/' + response.scriptName, + code: response.code + }; + } + async getHistory(ctx) { let items = await ctx.call('battleStore.find', { sort: '-createdAt', @@ -349,7 +376,7 @@ class LeagueService extends Service { return this.getLeagueSummary(ctx); } - async getLeagueSummary(ctx) { + async getUserRankTable(ctx) { const userId = ctx.meta.user ? ctx.meta.user.id : null; if(!userId) { throw new ValidationError('Not Authorized!', 401); @@ -370,12 +397,24 @@ class LeagueService extends Service { "history" ]; - let submission = await this.getUserSubmission(ctx) + let submission = await this.getUserSubmission(ctx); submission = _.pick(submission, fields); return { submission, - ranktable: this.ranktable.slice(submission.id, 7), + ranktable: this.ranktable.slice(submission.id, 7) + } + } + + async getLeagueSummary(ctx) { + const userId = ctx.meta.user ? ctx.meta.user.id : null; + if(!userId) { + throw new ValidationError('Not Authorized!', 401); + } + + let result = await this.getUserRankTable(ctx); + return { + ...result, history: await ctx.call('league.getHistory', {}) } } diff --git a/packages/jsbattle-webpage/features/schema.feature b/packages/jsbattle-webpage/features/schema.feature index f75f9423..c17b3b86 100644 --- a/packages/jsbattle-webpage/features/schema.feature +++ b/packages/jsbattle-webpage/features/schema.feature @@ -13,3 +13,4 @@ Feature: UBD Schema | 1 | | 2 | | 3 | + | 4 | diff --git a/packages/jsbattle-webpage/src/actions/sandboxAction.js b/packages/jsbattle-webpage/src/actions/sandboxAction.js index e5d3d147..1cf36cd2 100644 --- a/packages/jsbattle-webpage/src/actions/sandboxAction.js +++ b/packages/jsbattle-webpage/src/actions/sandboxAction.js @@ -127,12 +127,24 @@ export const getSandboxOpponentList = (useRemoteService) => { if(!result.ok) { throw new Error(`Error ${result.status}: ${result.statusText} (url: ${result.url})`); } - userTankList = await result.json(); - userTankList = userTankList.map((script) => ({ + result = await result.json(); + userTankList = result.map((script) => ({ id: script.id, - label: "sandbox/" + script.scriptName, + label: script.scriptName, source: 'remote_user' })); + result = await fetch("/api/user/league/ranktable", {}); + if(!result.ok) { + throw new Error(`Error ${result.status}: ${result.statusText} (url: ${result.url})`); + } + result = await result.json(); + result = result.ranktable.map((item) => ({ + id: item.id, + label: `${item.rank}. ${item.ownerName}/${item.scriptName}`, + source: 'league' + })); + userTankList = userTankList.concat(result); + } catch(err) { console.log(err); return dispatch({ @@ -144,7 +156,7 @@ export const getSandboxOpponentList = (useRemoteService) => { userTankList = await aiRepoService.getScriptNameList(); userTankList = userTankList.map((script) => ({ id: script.id, - label: 'sandbox/' + script.scriptName, + label: script.scriptName, source: 'local_user' })); } @@ -154,7 +166,7 @@ export const getSandboxOpponentList = (useRemoteService) => { bundledTanks = bundledTanks.map((name) => ({ id: name, - label: "jsbattle/" + name, + label: name, source: "bundled" })); @@ -197,6 +209,23 @@ export const setSandboxOpponent = (source, id) => { }); } break; + case 'league': + try { + script = await fetch("/api/user/league/scripts/" + id); + if(!script.ok) { + throw new Error(`Error ${script.status}: ${script.statusText} (url: ${script.url})`); + } + script = await script.json(); + scriptName = script.scriptName; + scriptCode = script.code; + } catch(err) { + console.log(err); + return dispatch({ + type: SANDBOX_OPPONENT_LIST_FAILURE, + payload: err + }); + } + break; default: throw new Error(`Not supported source ${source}`); } diff --git a/packages/jsbattle-webpage/src/components/LeagueJoin.js b/packages/jsbattle-webpage/src/components/LeagueJoin.js index a6bc86c7..6e3efc86 100644 --- a/packages/jsbattle-webpage/src/components/LeagueJoin.js +++ b/packages/jsbattle-webpage/src/components/LeagueJoin.js @@ -16,8 +16,14 @@ export default class LeagueJoin extends React.Component { submissionId = props.tankList[0].id; } + let rejoin; + if(this.props.selected) { + rejoin = (submissionId == this.props.selected.scriptId); + } + this.state = { editMode: false, + rejoin, newSubmissionId: submissionId, }; } @@ -35,7 +41,12 @@ export default class LeagueJoin extends React.Component { } onSubmissionChange(event) { - this.setState({newSubmissionId: event.target.value}); + let rejoin = false; + if(this.props.selected) { + rejoin = (event.target.value == this.props.selected.scriptId); + } + + this.setState({newSubmissionId: event.target.value, rejoin}); } openEditor() { @@ -68,7 +79,7 @@ export default class LeagueJoin extends React.Component { let leaveButton; let tankSelect; if(this.props.tankList.length > 0) { - joinButton =

; + joinButton =

; tankSelect =
your tank: this.props.onOpponentChange(this.props.opponents[e.target.value])}> - {opponents} - + + + + + + + +
diff --git a/packages/jsbattle-webpage/src/components/test/LiveCodeSandboxSettingsTab.spec.js b/packages/jsbattle-webpage/src/components/test/LiveCodeSandboxSettingsTab.spec.js index bf3f7bb9..01c91ede 100644 --- a/packages/jsbattle-webpage/src/components/test/LiveCodeSandboxSettingsTab.spec.js +++ b/packages/jsbattle-webpage/src/components/test/LiveCodeSandboxSettingsTab.spec.js @@ -1,3 +1,4 @@ +import 'babel-polyfill'; import React from 'react'; import {shallow} from 'enzyme'; import LiveCodeSandboxSettingsTab from '../LiveCodeSandboxSettingsTab.js'; @@ -10,22 +11,26 @@ test('LiveCodeSandboxSettingsTab renders properly', () => { test('list opponents', () => { const opponents = [ - { id: 'op1', label: 'alpha753' }, - { id: 'op2', label: 'beta78732' } + { id: 'op1', label: 'alpha753', source: 'bundled' }, + { id: 'op2', label: 'beta78732', source: 'league' }, + { id: 'op3', label: 'gamma8784', source: 'league' }, ] const wrapper = shallow(); - expect(wrapper.find('#opponent').text()).toMatch(/alpha753/) + expect(wrapper.find('#category').text()).toMatch(/League/) expect(wrapper.find('#opponent').text()).toMatch(/beta78732/) + expect(wrapper.find('#opponent').text()).toMatch(/gamma8784/) expect(wrapper.find('#opponent').props().value).toBe(1); }); -test('change opponent', () => { +test('change opponent', async () => { const opponents = [ - { id: 'op532234', label: 'alpha98324' }, - { id: 'op632243', label: 'beta77532' } + { id: 'op532234', label: 'alpha98324', source: 'league' }, + { id: 'op6543', label: 'omega98324', source: 'league' }, + { id: 'op2345', label: 'phi77532', source: 'bundled' }, + { id: 'op632243', label: 'beta77532', source: 'bundled' }, ]; const onOpponentChange = jest.fn(); const wrapper = shallow( { opponents={opponents} onOpponentChange={onOpponentChange} />); - wrapper.find('#opponent').simulate('change', {target: { value : 1}}); - expect(onOpponentChange.mock.calls).toHaveLength(1); - expect(onOpponentChange.mock.calls[0][0]).toHaveProperty('id', 'op632243'); + await wrapper.find('#category').simulate('change', {target: { value : 'league'}}); + await wrapper.find('#opponent').simulate('change', {target: { value : 1}}); + expect(onOpponentChange.mock.calls).toHaveLength(2); + expect(onOpponentChange.mock.calls[0][0]).toHaveProperty('id', 'op532234'); + expect(onOpponentChange.mock.calls[1][0]).toHaveProperty('id', 'op6543'); }); test('change mode', () => { diff --git a/packages/jsbattle-webpage/src/containers/SandboxScreen.js b/packages/jsbattle-webpage/src/containers/SandboxScreen.js index b32dd9e6..0919c2e9 100644 --- a/packages/jsbattle-webpage/src/containers/SandboxScreen.js +++ b/packages/jsbattle-webpage/src/containers/SandboxScreen.js @@ -76,6 +76,7 @@ export class SandboxScreen extends React.Component { case 'bundled': aiDef.fromFile(opponent.name); break; + case 'league': case 'local_user': case 'remote_user': aiDef.fromCode(opponent.name, opponent.code); @@ -136,17 +137,18 @@ export class SandboxScreen extends React.Component { renderSettingsTab() { let selectedOpponent; - if((this.props.opponent.source == 'local_user' || this.props.opponent.source == 'remote_user') && this.props.opponent.name == this.props.script.scriptName) { - selectedOpponent = {source: 'bundled', id: 'dummy'}; - } else { + let opponentList = this.props.opponentList.filter((opponent) => !((opponent.source == 'local_user' || opponent.source == 'remote_user') && opponent.label == this.props.script.scriptName)); + + if(opponentList.find((opponent) => opponent.source == this.props.opponent.source && opponent.id == this.props.opponent.id)) { selectedOpponent = {source: this.props.opponent.source, id: this.props.opponent.id}; + } else { + selectedOpponent = {source: 'bundled', id: 'dummy'}; } - return this.props.setSandboxBattleMode(isTeam)} onOpponentChange={(opponent) => this.onOpponentChange(opponent)}