diff --git a/backend/blueprints/spa_api/service_layers/player/play_style.py b/backend/blueprints/spa_api/service_layers/player/play_style.py index b3849490c..e7c8cf16b 100644 --- a/backend/blueprints/spa_api/service_layers/player/play_style.py +++ b/backend/blueprints/spa_api/service_layers/player/play_style.py @@ -50,7 +50,8 @@ def create_from_id(cls, id_: str, raw=False, rank=None, replay_ids=None, playlis ) @staticmethod - def create_all_stats_from_id(id_: str, rank=None, replay_ids=None) -> PlayerDataPoint: + def create_all_stats_from_id(id_: str, rank: int = None, replay_ids: List[str] = None, playlist: int = None, + win: bool = None) -> PlayerDataPoint: session = current_app.config['db']() game_count = player_wrapper.get_total_games(session, id_) if game_count == 0: @@ -59,7 +60,8 @@ def create_all_stats_from_id(id_: str, rank=None, replay_ids=None) -> PlayerData rank = get_rank(id_) averaged_stats, global_stats = player_stat_wrapper.get_averaged_stats(session, id_, redis=current_app.config['r'], raw=True, - rank=rank, replay_ids=replay_ids) + rank=rank, replay_ids=replay_ids, + playlist=playlist, win=win) playstyle_data_raw: PlayerDataPoint = PlayerDataPoint(name=id_, data_points=[ DataPoint(k, averaged_stats[k]) diff --git a/backend/blueprints/spa_api/service_layers/player/play_style_progression.py b/backend/blueprints/spa_api/service_layers/player/play_style_progression.py index 18ebfdb06..ab92b9e23 100644 --- a/backend/blueprints/spa_api/service_layers/player/play_style_progression.py +++ b/backend/blueprints/spa_api/service_layers/player/play_style_progression.py @@ -18,13 +18,14 @@ class PlayStyleProgression: def create_progression(id_: str, time_unit: TimeUnit = None, start_date: datetime.datetime = None, - end_date: datetime.datetime = None) -> List['PlayStyleProgressionDataPoint']: + end_date: datetime.datetime = None, + playlist: int = None) -> List['PlayStyleProgressionDataPoint']: session = current_app.config['db']() game_count = player_wrapper.get_total_games(session, id_) if game_count == 0: raise UserHasNoReplays() data = player_stat_wrapper.get_progression_stats(session, id_, time_unit=time_unit, start_date=start_date, - end_date=end_date) + end_date=end_date, playlist=playlist) session.close() return [ diff --git a/backend/blueprints/spa_api/spa_api.py b/backend/blueprints/spa_api/spa_api.py index 12d39f02f..49070b0a1 100644 --- a/backend/blueprints/spa_api/spa_api.py +++ b/backend/blueprints/spa_api/spa_api.py @@ -131,8 +131,12 @@ def api_get_player_play_style(id_): playlist = request.args['playlist'] else: playlist = 13 # standard - if 'win' in request.args: - win = bool(int(request.args['win'])) + if 'result' in request.args: + result = request.args['result'] + if result == 'win': + win = True + elif result == 'loss': + win = False else: win = None play_style_response = PlayStyleResponse.create_from_id(id_, raw='raw' in request.args, rank=rank, playlist=playlist, @@ -144,7 +148,8 @@ def api_get_player_play_style(id_): def api_get_player_play_style_all(id_): accepted_query_params = [ QueryParam(name='rank', optional=True, type_=int), - QueryParam(name='replay_ids', optional=True) + QueryParam(name='replay_ids', optional=True), + QueryParam(name='playlist', optional=True, type_=int), ] query_params = get_query_params(accepted_query_params, request) @@ -158,6 +163,7 @@ def api_get_player_play_style_progress(id_): QueryParam(name='time_unit', optional=True, type_=convert_to_enum(TimeUnit)), QueryParam(name='start_date', optional=True, type_=convert_to_datetime), QueryParam(name='end_date', optional=True, type_=convert_to_datetime), + QueryParam(name='playlist', optional=True, type_=int), ] query_params = get_query_params(accepted_query_params, request) diff --git a/backend/database/wrapper/stats/player_stat_wrapper.py b/backend/database/wrapper/stats/player_stat_wrapper.py index 300b61680..9ca229e91 100644 --- a/backend/database/wrapper/stats/player_stat_wrapper.py +++ b/backend/database/wrapper/stats/player_stat_wrapper.py @@ -89,7 +89,8 @@ def get_averaged_stats(self, session, id_: str, rank: int = None, redis=None, ra def get_progression_stats(self, session, id_, time_unit: 'TimeUnit' = TimeUnit.MONTH, start_date: datetime.datetime = None, - end_date: datetime.datetime = None): + end_date: datetime.datetime = None, + playlist: int = 13): if time_unit == TimeUnit.MONTH: date = func.to_char(Game.match_date, 'YY-MM') @@ -128,6 +129,9 @@ def get_progression_stats(self, session, id_, if end_date is not None: mean_query = mean_query.filter(Game.match_date < end_date) std_query = std_query.filter(Game.match_date < end_date) + if playlist is not None: + mean_query = mean_query.filter(Game.playlist == playlist) + std_query = std_query.filter(Game.playlist == playlist) mean_query = mean_query.all() std_query = std_query.all() diff --git a/webapp/src/Components/Player/Compare/PlayStyle/PlayerComparePlayStyleCharts.tsx b/webapp/src/Components/Player/Compare/PlayStyle/PlayerComparePlayStyleCharts.tsx index 46079cc84..2525873b2 100644 --- a/webapp/src/Components/Player/Compare/PlayStyle/PlayerComparePlayStyleCharts.tsx +++ b/webapp/src/Components/Player/Compare/PlayStyle/PlayerComparePlayStyleCharts.tsx @@ -2,12 +2,15 @@ import { FormControlLabel, Grid, Switch, Typography } from "@material-ui/core" import * as React from "react" import { PlayStyleRawResponse, PlayStyleResponse } from "src/Models" import { getPlayStyle, getPlayStyleRaw } from "../../../../Requests/Player/getPlayStyle" +import { PlaylistSelect } from "../../../Shared/Selects/PlaylistSelect" import { RankSelect } from "../../../Shared/Selects/RankSelect" import { PlayerPlayStyleChart } from "../../Overview/PlayStyle/PlayerPlayStyleChart" import { PlayerCompareTable } from "./PlayerCompareTable" interface Props { players: Player[] + playlist: number + handlePlaylistChange?: (playlist: number) => void } interface State { @@ -20,7 +23,12 @@ interface State { export class PlayerComparePlayStyleCharts extends React.PureComponent { constructor(props: Props) { super(props) - this.state = {playerPlayStyles: [], playerPlayStylesRaw: [], rank: -1, heatmapMode: false} + this.state = { + playerPlayStyles: [], + playerPlayStylesRaw: [], + rank: -1, + heatmapMode: false + } } public componentDidMount() { @@ -46,6 +54,9 @@ export class PlayerComparePlayStyleCharts extends React.PureComponent ) + + const dropDown = ( + + ) return ( <> @@ -75,6 +97,9 @@ export class PlayerComparePlayStyleCharts extends React.PureComponent + + {dropDown} + {chartTitles.map((chartTitle, i) => { return ( { const rank = this.state.rank === -1 ? undefined : this.state.rank - Promise.all(players.map((player) => getPlayStyle(player.id, rank))) + Promise.all(players.map((player) => getPlayStyle(player.id, rank, this.props.playlist))) .then((playerPlayStyles) => { if (reload) { this.setState({playerPlayStyles}) @@ -113,7 +138,7 @@ export class PlayerComparePlayStyleCharts extends React.PureComponent getPlayStyleRaw(player.id))) + Promise.all(players.map((player) => getPlayStyleRaw(player.id, this.props.playlist))) .then((playerPlayStylesRaw) => { if (reload) { this.setState({playerPlayStylesRaw}) @@ -142,4 +167,11 @@ export class PlayerComparePlayStyleCharts extends React.PureComponent { this.setState({heatmapMode}) } + + private readonly handlePlaylistsChange: React.ChangeEventHandler = (event) => { + const selectedPlaylist = event.target.value as any as number + if (this.props.handlePlaylistChange) { + this.props.handlePlaylistChange(selectedPlaylist) + } + } } diff --git a/webapp/src/Components/Player/Compare/PlayerCompareContent.tsx b/webapp/src/Components/Player/Compare/PlayerCompareContent.tsx index c8e33535d..1e15f806b 100644 --- a/webapp/src/Components/Player/Compare/PlayerCompareContent.tsx +++ b/webapp/src/Components/Player/Compare/PlayerCompareContent.tsx @@ -11,12 +11,13 @@ type PlayerCompareTab = "Current" | "Progression" interface State { selectedTab: PlayerCompareTab + playlist: number } export class PlayerCompareContent extends React.PureComponent { constructor(props: Props) { super(props) - this.state = {selectedTab: "Current"} + this.state = {selectedTab: "Current", playlist: 13} } public render() { @@ -33,13 +34,15 @@ export class PlayerCompareContent extends React.PureComponent { {this.state.selectedTab === "Current" ?
- +
:
- +
} @@ -50,4 +53,8 @@ export class PlayerCompareContent extends React.PureComponent { private readonly handleSelectTab = (event: React.ChangeEvent, selectedTab: PlayerCompareTab) => { this.setState({selectedTab}) } + + private readonly handlePlaylistChange = (playlist: number) => { + this.setState({playlist}) + } } diff --git a/webapp/src/Components/Player/Compare/Progression/PlayerProgressionCharts.tsx b/webapp/src/Components/Player/Compare/Progression/PlayerProgressionCharts.tsx index 71cfb52a7..db297d709 100644 --- a/webapp/src/Components/Player/Compare/Progression/PlayerProgressionCharts.tsx +++ b/webapp/src/Components/Player/Compare/Progression/PlayerProgressionCharts.tsx @@ -6,11 +6,14 @@ import { PlayStyleProgressionPoint } from "src/Models" import { getProgression } from "../../../../Requests/Player/getProgression" import { convertSnakeAndCamelCaseToReadable } from "../../../../Utils/String" import { ClearableDatePicker } from "../../../Shared/ClearableDatePicker" +import { PlaylistSelect } from "../../../Shared/Selects/PlaylistSelect" import { FieldSelect } from "./FieldSelect" import { ProgressionChart } from "./ProgressionChart" interface Props { players: Player[] + playlist: number + handlePlaylistChange?: (playlist: number) => void } export type TimeUnit = "day" | "month" | "quarter" | "year" @@ -23,6 +26,7 @@ interface State { startDate: moment.Moment | null endDate: moment.Moment | null timeUnit: "day" | "month" | "quarter" | "year" + playlist: number | null } export class PlayerProgressionCharts extends React.PureComponent { @@ -34,7 +38,8 @@ export class PlayerProgressionCharts extends React.PureComponent { selectedFields: [], startDate: null, endDate: null, - timeUnit: "month" + timeUnit: "month", + playlist: this.props.playlist } } @@ -65,7 +70,8 @@ export class PlayerProgressionCharts extends React.PureComponent { if ((this.state.timeUnit !== prevState.timeUnit) || (this.state.startDate !== prevState.startDate) - || (this.state.endDate !== prevState.endDate)) { + || (this.state.endDate !== prevState.endDate) + || (this.state.playlist !== prevState.playlist)) { this.refreshAllData() .then(this.updateFields) } @@ -82,10 +88,19 @@ export class PlayerProgressionCharts extends React.PureComponent { player: players[i], playStyleProgressionPoints })) - + const dropDown = ( + + ) return ( <> - + { label="End date"/> + + {dropDown} + {this.state.selectedFields.map((field) => { return ( { return getProgression(playerId, { timeUnit: this.state.timeUnit === null ? undefined : this.state.timeUnit, startDate: this.state.startDate === null ? undefined : this.state.startDate, - endDate: this.state.endDate === null ? undefined : this.state.endDate + endDate: this.state.endDate === null ? undefined : this.state.endDate, + playlist: this.state.playlist === null ? undefined : this.state.playlist }) } @@ -201,4 +220,12 @@ export class PlayerProgressionCharts extends React.PureComponent { private readonly handleTimeUnitChange: React.ChangeEventHandler = (event) => { this.setState({timeUnit: event.target.value as TimeUnit}) } + + private readonly handlePlaylistsChange: React.ChangeEventHandler = (event) => { + const selectedPlaylist = event.target.value as any as number + this.setState({playlist: selectedPlaylist}) + if (this.props.handlePlaylistChange) { + this.props.handlePlaylistChange(selectedPlaylist) + } + } } diff --git a/webapp/src/Components/Player/Overview/PlayStyle/PlayStyleActions.tsx b/webapp/src/Components/Player/Overview/PlayStyle/PlayStyleActions.tsx index bc59abfaf..7e761ee2e 100644 --- a/webapp/src/Components/Player/Overview/PlayStyle/PlayStyleActions.tsx +++ b/webapp/src/Components/Player/Overview/PlayStyle/PlayStyleActions.tsx @@ -1,4 +1,13 @@ -import { Button, Dialog, DialogContent, DialogTitle, Grid, IconButton, Tooltip } from "@material-ui/core" +import { + Button, + Dialog, + DialogContent, + DialogTitle, + FormControlLabel, + Grid, + IconButton, Switch, + Tooltip +} from "@material-ui/core" import CompareArrows from "@material-ui/icons/CompareArrows" import * as React from "react" import { Link } from "react-router-dom" @@ -11,6 +20,7 @@ interface OwnProps { player: Player useFullSizeCompareButton?: boolean handlePlaylistChange?: (playlist: number) => void + handleWinsLossesChange?: (winLossMode: boolean) => void } type Props = OwnProps @@ -18,12 +28,13 @@ type Props = OwnProps interface State { dialogOpen: boolean playlist: number + winLossMode: boolean } export class PlayStyleActions extends React.PureComponent { constructor(props: Props) { super(props) - this.state = {dialogOpen: false, playlist: 13} + this.state = {dialogOpen: false, playlist: 13, winLossMode: false} } public render() { @@ -43,7 +54,7 @@ export class PlayStyleActions extends React.PureComponent { ) const dropDown = ( { multiple={false}/> ) + const toggleWinsLosses = ( + } + label="Wins/Losses mode" + /> + ) + return ( + + {toggleWinsLosses} + {dropDown} @@ -97,4 +118,11 @@ export class PlayStyleActions extends React.PureComponent { this.props.handlePlaylistChange(selectedPlaylist) } } + + private readonly handleWinsLossesChange = (event: React.ChangeEvent, winLossMode: boolean) => { + this.setState({winLossMode}) + if (this.props.handleWinsLossesChange) { + this.props.handleWinsLossesChange(winLossMode) + } + } } diff --git a/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyle.tsx b/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyle.tsx index 5ceadac9e..6731f3674 100644 --- a/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyle.tsx +++ b/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyle.tsx @@ -9,6 +9,7 @@ import { PlayerPlayStyleChart } from "./PlayerPlayStyleChart" interface OwnProps { player: Player playlist?: number + winLossMode?: boolean } type Props = OwnProps @@ -16,6 +17,7 @@ type Props = OwnProps interface State { data?: PlayStyleResponse + winLossData?: PlayStyleResponse[] reloadSignal: boolean } @@ -32,6 +34,9 @@ class PlayerPlayStyleComponent extends React.PureComponent { if (prevProps.playlist !== this.props.playlist) { this.triggerReload() } + if (prevProps.winLossMode !== this.props.winLossMode) { + this.triggerReload() + } } public render() { @@ -41,7 +46,8 @@ class PlayerPlayStyleComponent extends React.PureComponent { "Upload more replays to get more accurate stats." return ( - + {this.state.data && <> {this.state.data.showWarning && @@ -57,18 +63,33 @@ class PlayerPlayStyleComponent extends React.PureComponent { } - {this.state.data.chartDatas.map((chartDataResponse) => { - return ( - - - {chartDataResponse.title} - - - - ) - })} - + {(this.props.winLossMode && this.state.winLossData) ? + this.state.winLossData[0].chartDatas.map((chartDataResponse, i) => { + return ( + + + {chartDataResponse.title} + + {this.state.winLossData && + + data.chartDatas[i])}/>} + + ) + }) + : + this.state.data.chartDatas.map((chartDataResponse) => { + return ( + + + {chartDataResponse.title} + + + + ) + })} } @@ -81,6 +102,12 @@ class PlayerPlayStyleComponent extends React.PureComponent { .then((data) => this.setState({data})) } + private readonly getPlayStylesWinLoss = (): Promise => { + const win = getPlayStyle(this.props.player.id, undefined, this.props.playlist, true) + const loss = getPlayStyle(this.props.player.id, undefined, this.props.playlist, false) + return Promise.all([win, loss]).then((winLossData) => this.setState({winLossData})) + } + private readonly triggerReload = () => { this.setState({reloadSignal: !this.state.reloadSignal}) } diff --git a/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyleCard.tsx b/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyleCard.tsx index c507e00d5..f74b5aa26 100644 --- a/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyleCard.tsx +++ b/webapp/src/Components/Player/Overview/PlayStyle/PlayerPlayStyleCard.tsx @@ -6,6 +6,7 @@ import { PlayStyleActions } from "./PlayStyleActions" interface Props { player: Player handlePlaylistChange?: (playlist: number) => void + handleWinsLossesChange?: (winLossMode: boolean) => void } export class PlayerPlayStyleCard extends React.PureComponent { @@ -25,7 +26,9 @@ export class PlayerPlayStyleCard extends React.PureComponent { action={ + handlePlaylistChange={this.props.handlePlaylistChange} + handleWinsLossesChange={this.props.handleWinsLossesChange} + /> }/> {this.props.children} diff --git a/webapp/src/Components/Player/PlayerOverview.tsx b/webapp/src/Components/Player/PlayerOverview.tsx index c673d1b16..04dc1f7f9 100644 --- a/webapp/src/Components/Player/PlayerOverview.tsx +++ b/webapp/src/Components/Player/PlayerOverview.tsx @@ -23,12 +23,13 @@ const playerViewTabs = ["Profile", "Playstyle", "Match History"] interface State { selectedMobileTab?: PlayerViewTab playlist?: number + winLossMode: boolean } class PlayerOverviewComponent extends React.PureComponent { constructor(props: Props) { super(props) - this.state = {selectedMobileTab: "Profile", playlist: 13} + this.state = {selectedMobileTab: "Profile", playlist: 13, winLossMode: false} } public render() { @@ -39,7 +40,10 @@ class PlayerOverviewComponent extends React.PureComponent { } const playerSideBar = - const playerPlayStyle = + const playerPlayStyle = ( + + ) const playerMatchHistory = ( { + handlePlaylistChange={this.handlePlaylistChange} + handleWinsLossesChange={this.handleWinsLossesChange} + > {playerPlayStyle} @@ -89,6 +95,7 @@ class PlayerOverviewComponent extends React.PureComponent { <> {playerPlayStyle} @@ -111,6 +118,10 @@ class PlayerOverviewComponent extends React.PureComponent { private readonly handlePlaylistChange = (playlist: number) => { this.setState({playlist}) } + + private readonly handleWinsLossesChange = (winLossMode: boolean) => { + this.setState({winLossMode}) + } } export const PlayerOverview = withWidth()(PlayerOverviewComponent) diff --git a/webapp/src/Components/Shared/Selects/PlaylistSelect.tsx b/webapp/src/Components/Shared/Selects/PlaylistSelect.tsx index 849f42412..d13544114 100644 --- a/webapp/src/Components/Shared/Selects/PlaylistSelect.tsx +++ b/webapp/src/Components/Shared/Selects/PlaylistSelect.tsx @@ -187,16 +187,25 @@ const playlists: PlaylistMetadata[] = [ ] interface OwnProps { - selectedPlaylists: number[] - handleChange: React.ChangeEventHandler helperText: string inputLabel: string dropdownOnly?: boolean currentPlaylistsOnly?: boolean - multiple: boolean } -type Props = OwnProps +interface MultiplePlaylistProps { + multiple: true + selectedPlaylists: number[] + handleChange: React.ChangeEventHandler +} + +interface SinglePlaylistProps { + multiple: false + selectedPlaylist: number + handleChange: React.ChangeEventHandler +} + +type Props = (SinglePlaylistProps | MultiplePlaylistProps) & OwnProps & WithStyles interface State { @@ -224,28 +233,29 @@ class PlaylistSelectComponent extends React.PureComponent { || (this.state.filterStandardMode !== prevState.filterStandardMode) || (this.state.filterCurrent !== prevState.filterCurrent)) { const filteredPlaylists = this.getFilteredPlaylists().map((playlist) => playlist.value) - const filteredSelectedPlaylists = _.intersection(this.props.selectedPlaylists, filteredPlaylists) - if (!_.isEqual(this.props.selectedPlaylists, filteredSelectedPlaylists)) { + const selectedPlaylists = this.props.multiple ? this.props.selectedPlaylists : [this.props.selectedPlaylist] + const filteredSelectedPlaylists = _.intersection(selectedPlaylists, filteredPlaylists) + if (!_.isEqual(selectedPlaylists, filteredSelectedPlaylists)) { this.setSelectedPlaylists(filteredSelectedPlaylists) } } } public render() { - const {classes, selectedPlaylists, handleChange, inputLabel, helperText} = this.props + const {classes, handleChange, inputLabel, helperText} = this.props const playlistsMultiSelect = ( {inputLabel}