Merge pull request #3 from vkaelin/feat/filter-season

New feature: filter matches and stats by season
This commit is contained in:
Valentin Kaelin 2020-02-01 20:18:53 +01:00 committed by GitHub
commit c7bb47dd07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 358 additions and 200 deletions

View file

@ -3,8 +3,8 @@
<Ripple <Ripple
@click.native="displayDetails" @click.native="displayDetails"
color="rgba(43, 108, 176, 0.7)" color="rgba(43, 108, 176, 0.7)"
:class="[data.result, showDetails ? 'rounded-t-lg' : 'rounded-lg']" :class="[data.result, showDetails ? 'rounded-t-lg' : 'rounded-lg', {'mt-4': indexMatch !== 0 }]"
class="match relative mt-4 bg-blue-800 text-white text-base cursor-pointer hover:shadow-xl" class="match relative bg-blue-800 text-white text-base cursor-pointer hover:shadow-xl"
> >
<div class="relative flex flex-wrap px-5 py-3"> <div class="relative flex flex-wrap px-5 py-3">
<div class="first w-4/12 text-left"> <div class="first w-4/12 text-left">
@ -169,6 +169,10 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
indexMatch: {
type: Number,
default: -1,
}
}, },
data() { data() {

View file

@ -0,0 +1,51 @@
<template>
<div class="relative group self-end inline-block text-blue-200 leading-none">
<select
v-model="season"
@change="filterSeason"
dir="rtl"
class="block appearance-none bg-transparent w-full px-4 pr-8 rounded-md cursor-pointer focus:outline-none group-hover:text-white"
>
<option :value="null" class="bg-blue-800">All seasons</option>
<option v-for="(s, index) in seasons" :key="index" :value="s" class="bg-blue-800">Season {{ s }}</option>
</select>
<div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
>
<svg class="w-4 h-4 text-blue-200 group-hover:text-white">
<use xlink:href="#caret-down" />
</svg>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex'
export default {
data() {
return {
season: null
}
},
computed: {
...mapState({
currentseason: state => state.summoner.basic.currentSeason,
seasons: state => state.summoner.basic.seasons,
}),
},
created() {
this.season = this.currentseason
},
methods: {
filterSeason() {
console.log('filter season', this.season)
this.updateSeason(this.season)
},
...mapActions('summoner', ['updateSeason'])
}
}
</script>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="text-white"> <div class="text-white pb-2">
<div class="flex justify-between"> <div class="flex justify-between">
<div style="width: 520px; height: 239px;"> <div style="width: 520px; height: 239px;">
<content-loader <content-loader

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="mt-3 flex text-center"> <div class="mt-3 flex text-center">
<div class="mt-4 w-3/12"> <div class="w-3/12">
<div class="bg-blue-850 rounded-lg" style="width: 300px; height: 339px;"> <div class="bg-blue-850 rounded-lg" style="width: 300px; height: 339px;">
<content-loader <content-loader
:height="339" :height="339"
@ -191,7 +191,8 @@
<div <div
v-for="index in 10" v-for="index in 10"
:key="index" :key="index"
class="mt-4 ml-4 bg-blue-850 rounded-lg" :class="{'mt-4': index !== 1}"
class="ml-4 bg-blue-850 rounded-lg"
style="width: 884px; height: 144px;" style="width: 884px; height: 144px;"
> >
<content-loader <content-loader

View file

@ -62,7 +62,9 @@
<RecentActivity :matches="basic.matchList" /> <RecentActivity :matches="basic.matchList" />
</div> </div>
</div> </div>
<div class="flex items-center justify-between">
<!-- NAVIGATION --> <!-- NAVIGATION -->
<div class="pb-2">
<router-link <router-link
:to="{ name: 'summoner', params: { region: $route.params.region, name: $route.params.name }}" :to="{ name: 'summoner', params: { region: $route.params.region, name: $route.params.name }}"
:class="isRouteActive('summoner')" :class="isRouteActive('summoner')"
@ -87,8 +89,14 @@
class="ml-4 pb-2 border-b-2 border-transparent text-blue-300 cursor-pointer hover:text-blue-100" class="ml-4 pb-2 border-b-2 border-transparent text-blue-300 cursor-pointer hover:text-blue-100"
exact exact
>live game</router-link> >live game</router-link>
</div>
<!-- Select Season -->
<FilterSeason />
</div>
</template> </template>
<!-- View --> <!-- View -->
<transition :name="tabTransition"> <transition :name="tabTransition">
<slot></slot> <slot></slot>
</transition> </transition>
@ -110,6 +118,7 @@
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex' import { mapState, mapActions, mapGetters } from 'vuex'
import FilterSeason from '@/components/Summoner/FilterSeason.vue'
import LazyBackground from '@/components/LazyBackgroundImage.vue' import LazyBackground from '@/components/LazyBackgroundImage.vue'
import MainFooter from '@/components/MainFooter.vue' import MainFooter from '@/components/MainFooter.vue'
import RecentActivity from '@/components/Summoner/RecentActivity.vue' import RecentActivity from '@/components/Summoner/RecentActivity.vue'
@ -119,6 +128,7 @@ import SummonerRanked from '@/components/Summoner/SummonerRanked.vue'
export default { export default {
components: { components: {
FilterSeason,
LazyBackground, LazyBackground,
MainFooter, MainFooter,
RecentActivity, RecentActivity,

View file

@ -1,4 +1,6 @@
import axiosHttp from 'axios' import axiosHttp from 'axios'
import router from '../router'
import store from '../store'
export const axios = axiosHttp export const axios = axiosHttp
@ -11,6 +13,14 @@ const axiosSource = CancelToken.source()
axios.defaults.axiosSource = axiosSource axios.defaults.axiosSource = axiosSource
axios.defaults.cancelToken = axiosSource.token axios.defaults.cancelToken = axiosSource.token
// Add season number to data if the route need it
axios.interceptors.request.use(function (config) {
if (config.url !== 'summoner-basic' && router.currentRoute.meta.season) {
config.data.season = store.state.summoner.basic.currentSeason
}
return config
})
export default { export default {
install(Vue) { install(Vue) {
Vue.prototype.$axios = axiosHttp Vue.prototype.$axios = axiosHttp

View file

@ -25,17 +25,26 @@ const router = new Router({
{ {
path: '/summoner/:region/:name', path: '/summoner/:region/:name',
name: 'summoner', name: 'summoner',
component: Summoner component: Summoner,
meta: {
season: true
}
}, },
{ {
path: '/summoner/:region/:name/champions', path: '/summoner/:region/:name/champions',
name: 'summonerChampions', name: 'summonerChampions',
component: SummonerChampions component: SummonerChampions,
meta: {
season: true
}
}, },
{ {
path: '/summoner/:region/:name/records', path: '/summoner/:region/:name/records',
name: 'summonerRecords', name: 'summonerRecords',
component: SummonerRecords component: SummonerRecords,
meta: {
season: true
}
}, },
{ {
path: '/summoner/:region/:name/live', path: '/summoner/:region/:name/live',

View file

@ -6,8 +6,10 @@ export const namespaced = true
export const state = { export const state = {
basic: { basic: {
account: {}, account: {},
currentSeason: null,
matchList: [], matchList: [],
ranked: {}, ranked: {},
seasons: [],
status: '', status: '',
}, },
overview: { overview: {
@ -35,6 +37,7 @@ export const state = {
export const mutations = { export const mutations = {
BASIC_REQUEST(state) { BASIC_REQUEST(state) {
state.basic.status = 'loading' state.basic.status = 'loading'
state.basic.currentSeason = null
state.champions.championsLoaded = false state.champions.championsLoaded = false
state.records.recordsLoaded = false state.records.recordsLoaded = false
state.overview.loaded = false state.overview.loaded = false
@ -79,6 +82,7 @@ export const mutations = {
state.basic.account = infos.account state.basic.account = infos.account
state.basic.matchList = infos.matchList state.basic.matchList = infos.matchList
state.basic.ranked = infos.ranked state.basic.ranked = infos.ranked
state.basic.seasons = infos.seasons
state.basic.status = 'found' state.basic.status = 'found'
state.live.match = infos.current state.live.match = infos.current
state.live.playing = infos.playing state.live.playing = infos.playing
@ -90,7 +94,14 @@ export const mutations = {
state.live.match = {} state.live.match = {}
state.live.playing = false state.live.playing = false
state.live.liveLoaded = false state.live.liveLoaded = false
} },
UPDATE_SEASON(state, { season }) {
state.basic.currentSeason = season
state.overview.loaded = false
state.champions.championsLoaded = false
state.records.recordsLoaded = false
},
} }
export const actions = { export const actions = {
@ -168,6 +179,9 @@ export const actions = {
const records = resp.data ? createRecordsData(resp.data) : {} const records = resp.data ? createRecordsData(resp.data) : {}
commit('RECORDS_FOUND', { records }) commit('RECORDS_FOUND', { records })
},
updateSeason({ commit }, season) {
commit('UPDATE_SEASON', { season })
} }
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<div v-if="overviewLoaded" key="overview" class="mt-3 flex text-center"> <div v-if="overviewLoaded" key="overview" class="mt-3 flex text-center">
<div class="mt-4 w-3/12"> <div class="w-3/12">
<SummonerChampions /> <SummonerChampions />
<SummonerStats /> <SummonerStats />
<SummonerMates /> <SummonerMates />
@ -15,6 +15,7 @@
v-for="(match, index) in overview.matches" v-for="(match, index) in overview.matches"
:key="index" :key="index"
:data="overview.matches[index]" :data="overview.matches[index]"
:index-match="index"
/> />
</ul> </ul>
<LoadingButton <LoadingButton
@ -62,6 +63,9 @@ export default {
}, },
watch: { watch: {
overviewLoaded() {
this.fetchData()
},
summonerFound() { summonerFound() {
this.fetchData() this.fetchData()
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<div key="champions" class="mt-3"> <div key="champions" class="mt-3">
<div class="mt-4 flex items-center"> <div class="flex items-center">
<ChampionsSearch @search-champions="updateSearch" /> <ChampionsSearch @search-champions="updateSearch" />
<FilterQueue @filter-queue="filterByQueue" :choices="queues" class="ml-4" /> <FilterQueue @filter-queue="filterByQueue" :choices="queues" class="ml-4" />
</div> </div>
@ -24,6 +24,7 @@ export default {
data() { data() {
return { return {
queue: null,
searchChampions: '' searchChampions: ''
} }
}, },
@ -53,6 +54,9 @@ export default {
}, },
watch: { watch: {
championsLoaded() {
this.fetchData()
},
summonerFound() { summonerFound() {
this.fetchData() this.fetchData()
} }
@ -65,13 +69,13 @@ export default {
methods: { methods: {
fetchData() { fetchData() {
if (!this.championsLoaded && this.summonerFound) { if (!this.championsLoaded && this.summonerFound) {
this.championsRequest() this.championsRequest(this.queue)
} }
}, },
filterByQueue(queue) { filterByQueue(queue) {
queue = Number(queue) queue = Number(queue)
queue = queue === -1 ? null : queue this.queue = queue === -1 ? null : queue
this.championsRequest(queue) this.championsRequest(this.queue)
}, },
updateSearch(search) { updateSearch(search) {
this.searchChampions = search this.searchChampions = search

View file

@ -1,7 +1,6 @@
<template> <template>
<div key="records" class="mt-3"> <div key="records">
<template v-if="!recordsLoaded || (recordsLoaded && records.maxKda)"> <template v-if="!recordsLoaded || (recordsLoaded && records.maxKda)">
<div class="mt-4">
<div class="mx-4 text-blue-200 text-2xl border-b-2 border-blue-800 blue-900">basics</div> <div class="mx-4 text-blue-200 text-2xl border-b-2 border-blue-800 blue-900">basics</div>
<div class="-mx-2 flex flex-wrap"> <div class="-mx-2 flex flex-wrap">
<template v-if="recordsLoaded"> <template v-if="recordsLoaded">
@ -145,7 +144,6 @@
</div> </div>
</template> </template>
</div> </div>
</div>
</template> </template>
<template v-if="recordsLoaded && !records.maxKda"> <template v-if="recordsLoaded && !records.maxKda">
<div class="mt-4 flex flex-col items-center"> <div class="mt-4 flex flex-col items-center">
@ -176,6 +174,9 @@ export default {
}, },
watch: { watch: {
recordsLoaded() {
this.fetchData()
},
summonerFound() { summonerFound() {
this.fetchData() this.fetchData()
} }

View file

@ -488,7 +488,7 @@ module.exports = {
stroke: ['responsive'], stroke: ['responsive'],
tableLayout: ['responsive'], tableLayout: ['responsive'],
textAlign: ['responsive'], textAlign: ['responsive'],
textColor: ['responsive', 'hover', 'focus'], textColor: ['responsive', 'hover', 'focus', 'group-hover'],
textDecoration: ['responsive', 'hover', 'focus'], textDecoration: ['responsive', 'hover', 'focus'],
textTransform: ['responsive'], textTransform: ['responsive'],
userSelect: ['responsive'], userSelect: ['responsive'],

View file

@ -9,6 +9,10 @@ const StatsService = use('App/Services/StatsService')
const Summoner = use('App/Models/Summoner') const Summoner = use('App/Models/Summoner')
class SummonerController { class SummonerController {
async _getSeasons(puuid) {
let seasons = await MatchRepository.seasons(puuid)
return seasons.length ? seasons.map(s => s._id) : [10]
}
/** /**
* POST: get basic summoner data * POST: get basic summoner data
*/ */
@ -44,6 +48,9 @@ class SummonerController {
const matchList = summonerDB.matchList const matchList = summonerDB.matchList
finalJSON.matchList = matchList finalJSON.matchList = matchList
// All seasons the summoner has played
finalJSON.seasons = await this._getSeasons(account.puuid)
// CURRENT GAME // CURRENT GAME
const currentGame = await Jax.Spectator.summonerID(account.id, region) const currentGame = await Jax.Spectator.summonerID(account.id, region)
finalJSON.playing = !!currentGame finalJSON.playing = !!currentGame
@ -70,6 +77,7 @@ class SummonerController {
async overview({ request, response }) { async overview({ request, response }) {
console.time('overview') console.time('overview')
const account = request.input('account') const account = request.input('account')
const season = request.input('season')
const finalJSON = {} const finalJSON = {}
// Summoner in DB // Summoner in DB
@ -79,12 +87,17 @@ class SummonerController {
) )
// MATCHES BASIC // MATCHES BASIC
const gameIds = summonerDB.matchList.slice(0, 10).map(({ gameId }) => gameId) const gameIds = summonerDB.matchList.slice(0)
.filter(m => {
return season ? m.seasonMatch === season : true
})
.slice(0, 10)
.map(({ gameId }) => gameId)
finalJSON.matchesDetails = await MatchService.getMatches(account, gameIds, summonerDB) finalJSON.matchesDetails = await MatchService.getMatches(account, gameIds, summonerDB)
// STATS // STATS
console.time('STATS') console.time('STATS')
finalJSON.stats = await StatsService.getSummonerStats(account) finalJSON.stats = await StatsService.getSummonerStats(account, season)
console.timeEnd('STATS') console.timeEnd('STATS')
// SAVE IN DB // SAVE IN DB
@ -100,8 +113,9 @@ class SummonerController {
async champions({ request, response }) { async champions({ request, response }) {
const puuid = request.input('puuid') const puuid = request.input('puuid')
const queue = request.input('queue') const queue = request.input('queue')
const season = request.input('season')
console.time('championsRequest') console.time('championsRequest')
const championStats = await MatchRepository.championCompleteStats(puuid, queue) const championStats = await MatchRepository.championCompleteStats(puuid, queue, season)
console.timeEnd('championsRequest') console.timeEnd('championsRequest')
return response.json(championStats) return response.json(championStats)
} }
@ -111,8 +125,9 @@ class SummonerController {
*/ */
async records({ request, response }) { async records({ request, response }) {
const puuid = request.input('puuid') const puuid = request.input('puuid')
const season = request.input('season')
console.time('recordsRequest') console.time('recordsRequest')
const records = await MatchRepository.records(puuid) const records = await MatchRepository.records(puuid, season)
console.timeEnd('recordsRequest') console.timeEnd('recordsRequest')
return response.json(records[0]) return response.json(records[0])
} }

View file

@ -9,6 +9,19 @@ class MatchRepository {
this.Match = Match this.Match = Match
} }
/**
* Basic matchParams used in a lot of requests
* @param puuid of the summoner
*/
_matchParams(puuid) {
return {
summoner_puuid: puuid,
result: { $not: { $eq: 'Remake' } },
gamemode: { $nin: [800, 810, 820, 830, 840, 850] },
season: this.season ? this.season : { $exists: true }
}
}
/** /**
* Build the aggregate mongo query * Build the aggregate mongo query
* @param {Number} puuid * @param {Number} puuid
@ -22,9 +35,7 @@ class MatchRepository {
return this.Match.query().aggregate([ return this.Match.query().aggregate([
{ {
$match: { $match: {
summoner_puuid: puuid, ...this._matchParams(puuid),
result: { $not: { $eq: 'Remake' } },
gamemode: { $nin: [800, 810, 820, 830, 840, 850] },
...matchParams ...matchParams
} }
}, },
@ -82,11 +93,15 @@ class MatchRepository {
* Get Summoner's complete statistics for the all played champs * Get Summoner's complete statistics for the all played champs
* @param puuid of the summoner * @param puuid of the summoner
* @param queue of the matches to fetch, if null get all matches * @param queue of the matches to fetch, if null get all matches
* @param season of the matches to fetch, if null get all seasons
*/ */
championCompleteStats(puuid, queue) { championCompleteStats(puuid, queue, season) {
const matchParams = queue ? { const matchParams = {}
gamemode: { $eq: Number(queue) }, if (queue) {
} : {} matchParams.gamemode = { $eq: Number(queue) }
}
this.season = season
const groupParams = { const groupParams = {
time: { $sum: '$time' }, time: { $sum: '$time' },
gameLength: { $avg: '$time' }, gameLength: { $avg: '$time' },
@ -135,14 +150,15 @@ class MatchRepository {
/** /**
* Get Summoner's all records * Get Summoner's all records
* @param puuid of the summoner * @param puuid of the summoner
* @param season of the matches to fetch, if null get all seasons
*/ */
records(puuid) { records(puuid, season) {
this.season = season
return this.Match.query().aggregate([ return this.Match.query().aggregate([
{ {
$match: { $match: {
summoner_puuid: puuid, ...this._matchParams(puuid),
result: { $not: { $eq: 'Remake' } },
gamemode: { $nin: [800, 810, 820, 830, 840, 850] },
} }
}, },
{ {
@ -222,6 +238,24 @@ class MatchRepository {
] ]
return this._aggregate(puuid, matchParams, [], '$role', {}, finalSteps) return this._aggregate(puuid, matchParams, [], '$role', {}, finalSteps)
} }
/**
* Get Summoner's played seasons
* @param puuid of the summoner
*/
seasons(puuid) {
this.season = null
return this.Match.query().aggregate([
{
$match: {
...this._matchParams(puuid),
}
},
{
$group: { _id: '$season' }
},
])
}
/** /**
* Get Summoner's mates list * Get Summoner's mates list

View file

@ -8,7 +8,8 @@ class StatsService {
this.matchRepository = MatchRepository this.matchRepository = MatchRepository
} }
async getSummonerStats(account) { async getSummonerStats(account, season) {
this.matchRepository.season = season
const globalStats = await this.matchRepository.globalStats(account.puuid) const globalStats = await this.matchRepository.globalStats(account.puuid)
const gamemodeStats = await this.matchRepository.gamemodeStats(account.puuid) const gamemodeStats = await this.matchRepository.gamemodeStats(account.puuid)
const roleStats = await this.matchRepository.roleStats(account.puuid) const roleStats = await this.matchRepository.roleStats(account.puuid)