BZWC6XMOUXEPOI7P65MREJZ45TSQRFMMR2SQ7LML7RMCKUL57VHAC
7QS7R2ORWCZMTCE3SD5PJ5ZL27N3RS7YCWBHXH3JDPTHDQ3KBAMQC
B6QIBRKFDFZKHHHT6F6M2NST2CYXNK4FCCYL3JOIAYDZYX3FQLRAC
N7TZV5WJKLYKSG6SIVAID4ZCC76YHZU5U5WWQGG6TFKAV6CE3Y6AC
B7W4Q522DLB6DKH2TFDOCTSZFZLFTOLCT6CCZEOC3V3UUMSAOOFAC
GGREOYIRZE2GH62X7YXQA7IUSSB25ENLQ5OKVJYWPNI462DP37FAC
HXRDRHIVGSBEAMTKJFZK43MSB53BKON6F77AARUQNWFUPSTZSBPAC
Q4BYGYNX3UZXX47EMX2GWX6M6ICSMIMCLQ7HVOYS4HB4RHYI4NEQC
62OPHDLT2IIHK2OEH76NWB6V7E2Z5USUBNHB4X3DP2XLDI3YHY7QC
3QZU4ZIBA24ZXKPBGMTWX4S2GF6AOXJMPD2753CCTU7QUL2WMDXAC
}
#[derive(Debug, Insertable)]
#[table_name = "encounter_stats"]
pub struct EncounterStats {
pub team: i32,
pub encounter_id: i32,
pub difficulty: i32,
pub kill_log: Option<i32>,
pub kill_week: Option<i32>,
pub ilvl_min: f32,
pub ilvl_max: f32,
pub pull_count: i32,
pub prog_time: i32,
}
#[derive(Debug, Insertable, Queryable)]
#[table_name = "encounter_stats"]
pub struct EncounterStats {
pub team: i32,
pub encounter_id: i32,
pub difficulty: i32,
pub kill_log: Option<i32>,
pub kill_week: Option<i32>,
pub ilvl_min: f32,
pub ilvl_max: f32,
pub pull_count: i32,
pub prog_time: i32,
#[Object]
impl EncounterStats {
async fn team(&self) -> i32 {
self.team
}
async fn encounter_id(&self) -> i32 {
self.encounter_id
}
async fn difficulty(&self) -> i32 {
self.difficulty
}
async fn kill_log(&self, ctx: &Context<'_>) -> Result<Option<Log>> {
use crate::schema::logs::dsl as logs;
if self.kill_log.is_none() {
return Ok(None);
}
let Data { conpool, .. } = ctx.data()?;
let log = logs::logs
.filter(logs::iid.eq(self.kill_log.unwrap()))
.first(&*conpool.get()?)?;
Ok(Some(log))
}
async fn kill_week(&self) -> Option<i32> {
self.kill_week
}
async fn ilvl_min(&self) -> f32 {
self.ilvl_min
}
async fn ilvl_max(&self) -> f32 {
self.ilvl_max
}
async fn pull_count(&self) -> i32 {
self.pull_count
}
async fn prog_time(&self) -> i32 {
self.prog_time
}
}
Ok(logs)
match include_farm {
Some(true) => Ok(logs),
_ => Ok(prog_logs(&*conpool.get()?, encounter_id, difficulty, logs)?.logs),
}
}
async fn stats_for_encounter(
&self,
ctx: &Context<'_>,
team_id: i32,
encounter_id: i32,
difficulty: i32,
) -> Result<Option<EncounterStats>> {
use crate::schema::encounter_stats::dsl as stats;
let Data { conpool, .. } = ctx.data()?;
Ok(stats::encounter_stats
.filter(
stats::team
.eq(team_id)
.and(stats::encounter_id.eq(encounter_id))
.and(stats::difficulty.eq(difficulty)),
)
.select((
stats::team,
stats::encounter_id,
stats::difficulty,
stats::kill_log,
stats::kill_week,
stats::ilvl_min,
stats::ilvl_max,
stats::pull_count,
stats::prog_time,
))
.first(&*conpool.get()?)
.optional()?)
generate_analysis(&*con, guild_id.id() as i32)?;
match force_analysis {
Some(true) => generate_analysis(&*con, guild_id.id() as i32)?,
_ => {
for (encounter, difficulty) in new_encounters {
if let Some(difficulty) = difficulty {
generate_encounter_analysis(
&*con,
guild_id.id() as i32,
encounter as i32,
difficulty as i32,
)?
}
}
}
}
background_import(client.clone(), token.clone(), con, tx, guild_id, tag, zone)
.inspect_err(|e| {
error!("an error occurred during import: {:?}", e);
}),
background_import(
client.clone(),
token.clone(),
con,
tx,
guild_id,
tag,
zone,
force_analysis,
)
.inspect_err(|e| {
error!("an error occurred during import: {:?}", e);
}),
// Generate the analysis entries for a team on the given encounter and difficulty. This overwites
// any existing analysis for that 3-tuple.
pub fn generate_encounter_analysis(
pub struct ProgData {
pub logs: Vec<Log>,
pub kill: bool,
pub fights: HashMap<i32, Vec<Fight>>,
}
pub fn prog_logs(
Ok(ProgData {
logs,
kill,
fights: fightmap,
})
}
// Generate the analysis entries for a team on the given encounter and difficulty. This overwites
// any existing analysis for that 3-tuple.
pub fn generate_encounter_analysis(
con: &PgConnection,
team_id: i32,
encounter_id: i32,
difficulty: i32,
) -> Result<()> {
let raw_logs = logs_for_encounter(con, team_id, encounter_id, difficulty)?;
let region = team_region(con, team_id)?;
debug!(
"generating analysis for {} {} {}",
team_id, encounter_id, difficulty
);
let ProgData {
kill,
logs,
fights: fightmap,
} = prog_logs(con, encounter_id, difficulty, raw_logs)?;
}
}
`;
export const Q_STATS = gql`
query Stats($team: Int!, $encounterId: Int!, $difficulty: Int!) {
statsForEncounter(teamId: $team, encounterId: $encounterId, difficulty: $difficulty) {
team, encounterId, difficulty, killLog { iid, startTime }, killWeek, pullCount, progTime, ilvlMin, ilvlMax
<span className="block -mb-2">{DIFFICULTY_NAMES[props.difficulty] || props.difficulty}</span>
<span className="text-5xl">{ENCOUNTER_NAMES[props.encounterId] || props.encounterId}</span>
<span className="block">{DIFFICULTY_NAMES[props.difficulty] || props.difficulty}</span>
<span className="text-5xl" style={{lineHeight: 0.75}}>{ENCOUNTER_NAMES[props.encounterId] || props.encounterId}</span>
export function encounter_stats(props: StatProps): EncounterStats {
const killLog = props.logs.find(log => log.fights.some(({ kill }) => kill));
const ilvlRange = props.logs.reduce((acc, log) => {
return log.fights.reduce(([min, max], fight) => ([Math.min(min, fight.avgIlvl), Math.max(max, fight.avgIlvl)]), acc);
}, [1000, 0]);
const pullCount = props.logs.reduce((count, log) => count + log.fights.length, 0);
function EncounterStatColumn(props: StatProps) {
const { loading, error, data } = useQuery(Q_STATS, { variables: props });
const progTime = props.logs.reduce((time, log) => {
const duration_ms = log.fights[log.fights.length - 1].endTime - log.fights[0].startTime;
return time + duration_ms;
}, 0);
const killWeek = killLog ? tierWeek(killLog.zone, killLog.startTime, props.difficulty === 5, props.region) : undefined;
return {
encounterId: props.logs[0].fights[0].encounterId,
killLog, ilvlRange: ilvlRange as [number, number], pullCount, progTime, killWeek
if (loading) {
return <>Loading...</>;
useEffect(() => {
if(dispatch) {
dispatch(stats);
}
// the empty dep list is intentional. we do this once on load.
// eslint-disable-next-line
}, []);
const { killLog, ilvlMin, ilvlMax, pullCount, progTime, killWeek } = data.statsForEncounter as EncounterStats;
import EncounterOverview, { EncounterStats } from './EncounterOverview';
import { StatBlocksDispatch, ZONE_ENCOUNTERS, toRegion, ENCOUNTER_NAMES, DIFFICULTY_NAMES } from './model';
import EncounterOverview, { Q_STATS } from './EncounterOverview';
import { ZONE_ENCOUNTERS, toRegion, ENCOUNTER_NAMES, DIFFICULTY_NAMES } from './model';
function StatTable({ statblocks, encounters }: { statblocks: StatBlocks, encounters: number[] }) {
if(data && data.statsForEncounter) {
const stats = data.statsForEncounter;
return (
<tr>
<td className="text-left pr-2 border-r border-gray-200">{ENCOUNTER_NAMES[stats.encounterId]}</td>
<td className="pl-2 pr-2 text-center">{(stats && stats.killLog) ? formatShortDate(new Date(stats.killLog.startTime)) : ''}</td>
<td className="pl-2 pr-2 text-center">{(stats && stats.killWeek) ? `Week ${stats.killWeek}` : ''}</td>
<td className="pl-2 pr-2 border-l border-gray-200">{stats ? stats.pullCount : ''}</td>
<td className="pl-2 pr-2">{stats ? formatDuration(stats.progTime, ' hrs') : ''}</td>
<td className="pl-2 pr-2 border-l border-gray-200">{stats ? stats.ilvlMin.toFixed(1) : ''}</td>
<td className="pl-2 pr-2">{stats ? stats.ilvlMax.toFixed(1) : ''}</td>
</tr>
);
} else {
return null;
}
}
function StatTable({ difficulty, team, encounters }: { team: number; encounters: number[]; difficulty: number; }) {
{encounters.map(id => {
const stats = statblocks[id];
return (
<tr key={id}>
<td className="text-left pr-2 border-r border-gray-200">{ENCOUNTER_NAMES[id]}</td>
<td className="pl-2 pr-2 text-center">{(stats && stats.killLog) ? formatShortDate(stats.killLog.startTime) : ''}</td>
<td className="pl-2 pr-2 text-center">{(stats && stats.killWeek) ? `Week ${stats.killWeek}` : ''}</td>
<td className="pl-2 pr-2 border-l border-gray-200">{stats ? stats.pullCount : ''}</td>
<td className="pl-2 pr-2">{stats ? formatDuration(stats.progTime, ' hrs') : ''}</td>
<td className="pl-2 pr-2 border-l border-gray-200">{stats ? stats.ilvlRange[0].toFixed(1) : ''}</td>
<td className="pl-2 pr-2">{stats ? stats.ilvlRange[1].toFixed(1) : ''}</td>
</tr>
);
})}
{encounters.map(id => <StatRow key={id} encounterId={id} difficulty={difficulty} team={team} />)}
<StatBlocksDispatch.Provider value={dispatch} key={encounter}>
<EncounterOverview encounterId={encounter} difficulty={5} team={team.id} region={toRegion(region)} />
</StatBlocksDispatch.Provider>
<EncounterOverview key={encounter} encounterId={encounter} difficulty={5} team={team.id} region={toRegion(region)} />