Module:PlayerTeamHistoryAbstract
Documentation for this module may be created at Module:PlayerTeamHistoryAbstract/doc
local util_args = require('Module:ArgsUtil')
local util_cargo = require("Module:CargoUtil")
local util_esports = require("Module:EsportsUtil")
local util_html = require("Module:HtmlUtil")
local util_map = require("Module:MapUtil")
local util_math = require("Module:MathUtil")
local util_news = require("Module:NewsUtil")
local util_source = require("Module:SourceUtil")
local util_table = require("Module:TableUtil")
local util_text = require("Module:TextUtil")
local util_time = require("Module:TimeUtil")
local util_timedelta = require("Module:TimedeltaUtil")
local util_vars = require("Module:VarsUtil")
local i18n = require('Module:i18nUtil')
local lang = mw.getLanguage('en')
local m_team = require('Module:Team')
local p = require('Module:GroupedRosterChangesAbstract'):extends()
local h = {}
function p:init(name)
self:super('init', name)
self.STATUSES_TO_IGNORE = {
set_to_leave = true,
opportunities = true,
}
self.PRELOADS_TO_IGNORE = {
'extended', 'gcd_extended', 'remain', 'expire_notleave', 'set_to_leave', 'opportunities', 'set_to_leave_already_joined', 'gcd_expire_notleave', 're_sign', 'gcd_remove_notleave',
leave = { 'retirement_no_team' },
join = { 'gcd_expire_notleave_yet', },
}
self.INCLUDE_STATUS = false
self.DEBUG = false
self.CONTRACT_DATES = {}
end
function p:run(args)
util_cargo.setStoreNamespace('')
h.castArgs(args)
self:setConstants(args)
self:setDebugIfEnabled()
return self:super('run', args)
end
function h.castArgs(args)
args.player = util_args.strOrTitle(args.player)
end
function p:setConstants(args)
if util_args.castAsBool(args.debug) then
self.DEBUG = true
end
end
function p:setDebugIfEnabled()
if self.DEBUG then
self.COLUMNS[#self.COLUMNS+1] = 'KeyJoin'
self.COLUMNS[#self.COLUMNS+1] = 'KeyLeave'
self.COLUMNS[#self.COLUMNS+1] = 'PreloadJoin'
self.COLUMNS[#self.COLUMNS+1] = 'PreloadLeave'
self.COLUMNS[#self.COLUMNS+1] = 'SisterTeamPage'
self.COLUMNS[#self.COLUMNS+1] = 'Status'
-- self.COLUMNS[#self.COLUMNS+1] = 'RoleModifier'
-- self.COLUMNS[#self.COLUMNS+1] = 'NextTeam'
self.COLUMNS[#self.COLUMNS+1] = 'changeIndex'
end
end
function p:getTables()
local ret = {
'NewsItems__Players',
'NewsItems=News',
'PlayerRedirects=PR',
'RosterChanges=RC',
'PlayerRenames=RN',
'ResidencyChanges=ResChange',
'Contracts',
'TeamRedirects=TRed1', -- join from contracts to ST1
'TeamRedirects=TRed2', -- join from news to ST2
'SisterTeams=ST1', -- the sister team page (if one exists) of the contract team
'SisterTeams=ST2', -- the sister team page (if one exists) of the news team
}
return ret
end
function p:getJoin()
-- remember this join is JUST for retrieving fields
-- so we dont have to worry about one-to-many conditions really, because nothing is happening
-- in any where conditions
-- so all of the redirects and stuff just discovering the target
-- not then also joining to another field making a many:many
local ret = {
-- joins to associate all of the relevant news in order
'NewsItems__Players._rowID=News._ID',
'NewsItems__Players._value=PR.AllName',
'News.NewsId=RC.NewsId',
'News.NewsId=RN.NewsId',
'News.NewsId=ResChange.NewsId',
'News.NewsId=Contracts.NewsId',
-- contracts need to respect contracts from the same org
'Contracts.Team=TRed1.AllName',
'TRed1._pageName=ST1.Team',
-- roster changes need to discover the org so we can pull contracts
'RC.Team=TRed2.AllName',
'TRed2._pageName=ST2.Team',
}
return ret
end
function p:getWhere(args)
local tbl = {
('PR._pageName="%s"'):format(args.player),
util_news.getExcludedPreloadsWhereCondition(self.PRELOADS_TO_IGNORE),
}
util_table.mergeArrays(tbl, h.getCorrectPlayerWhereConditions())
return util_cargo.concatWhere(tbl)
end
function h.getCorrectPlayerWhereConditions()
local tablesNeedingConditions = { 'RC', 'Contracts' }
return util_map.inPlace(tablesNeedingConditions, h.getOneCorrectPlayerWhereCondition)
end
function h.getOneCorrectPlayerWhereCondition(tbl)
return ('%s.Player=NewsItems__Players._value OR %s.Player IS NULL'):format(tbl, tbl)
end
function p:getFields()
local fields = self:super('getFields')
local newFields = {
'RN.OriginalName=NameOld',
'ResChange.ResidencyOld=ResidencyOld',
'News._pageName=ResPageName',
'Contracts.ContractEnd',
'Contracts.IsRemoval=IsContractRemoval',
'TRed2._pageName=TeamPage',
-- this originally was ST2._pageName before Contracts.Team but i think that's wrong
-- im changing it despite not finding any bug affecting it
-- but im 99% sure there's only no bug, because all teams with contracts happen to have sister teams too
'COALESCE(ST1._pageName, Contracts.Team, ST2._pageName, RC.Team)=SisterTeamPage',
}
return util_table.mergeArrays(fields, newFields)
end
function p:getGroupBy()
return 'COALESCE(RC.RosterChangeId, News.NewsId)'
end
-- format raw data rows
function p:formatOneNonRCRow(row)
if row.IsContractRemoval then
self.CONTRACT_DATES[row.SisterTeamPage] = nil
end
if not row.ContractEnd then return end
self.CONTRACT_DATES[row.SisterTeamPage] = row.ContractEnd
end
function p:formatOneRawDataRow(row)
self:super('formatOneRawDataRow', row)
-- dealing with contracts here will bring information *forward* when a player is on same sister team
-- and we want to preserve the fact that they had a contract already
-- this construction is done as we sort through the changes and apply formatting
-- later on we'll go back through the entire thing a 2nd time to do "after-the-fact" information
-- which will apply "normal" contract information, when contract info is stored *after* the
-- joining-the-team roster change in question
-- because maps happen forward, we can just do this in order
-- CONTRACT_DATES is the repository of info we maintain moving forward
local potentialContractDate = self.CONTRACT_DATES[row.SisterTeamPage]
if row.Date_SortJoin and potentialContractDate and potentialContractDate > row.Date_SortJoin then
row.ContractEnd = potentialContractDate
end
end
function p:getKey(row)
local key = ('%s%s%s%s'):format(
row.Team or '',
row.RoleModifier or 'Normal',
row.Role or 'Unknown',
self:getAndSetMeaningfulStatus(row)
)
return key
end
function p:getAndSetMeaningfulStatus(row)
if self.STATUSES_TO_IGNORE[row.Status] then
row.Status = nil
end
return row.Status or ''
end
-- group stuff
function p:updateAncillaryInformation(changesByLine, queryRow)
-- this is now propagating stuff backwards
-- for each new line we add, we go back through our entire output and check if we want to update anything
-- this has to be done in order, not all at once in the end, because information changes over time
-- so for the most part we'll go back and say, "what teams is the player CURRENTLY on at time of news?"
-- and then update this piece of information (e.g. residency change) on all of them
-- but we'll leave any rows alone that already have departures
for _, outputRow in ipairs(changesByLine) do
h.updateRowAncillaryInformationIfNeeded(outputRow, queryRow)
end
end
function h.updateRowAncillaryInformationIfNeeded(outputRow, queryRow)
if not outputRow.TeamLeave then
-- information that needs to get updated when the player is CURRENTLY on
-- the team. This includes contract information.
h.updateAncillaryInformationOnCurrentTeamsIfNeeded(outputRow, queryRow)
-- do not continue to do the only-after-they-left stuff
return
end
-- these are PRE-CHANGE values, so when they occur it means only apply them
-- when you already left the team
-- if you're still on the team, either the NEXT old one will apply, or the one in infobox will apply
for _, v in ipairs({ 'NameOld', 'ResidencyOld', }) do
h.updateAncillaryValueInRow(outputRow, queryRow, v)
end
end
function h.updateAncillaryInformationOnCurrentTeamsIfNeeded(outputRow, queryRow)
-- this is part of the propagating information backward process
if queryRow.ContractEnd and queryRow.SisterTeamPage == outputRow.SisterTeamPage then
outputRow.ContractEnd = queryRow.ContractEnd
end
end
function h.updateAncillaryValueInRow(outputRow, queryRow, value)
-- don't overwrite. also it's possible value is nil but whatever.
outputRow[value] = outputRow[value] or queryRow[value]
end
function p:isOriginalNews(changesByLine, row, index)
if not index then return true end
if not index then return true end
for _, oldChangeLineKey in ipairs(index) do
local oldChange = self:getOldChangeFromIndex(changesByLine, oldChangeLineKey)
if h.isThisLineARepeatOfAnOldOne(row, oldChange) then
return false
end
end
return true
end
function h.isThisLineARepeatOfAnOldOne(row, oldRow)
if row.TeamJoin and not oldRow.TeamLeave then
return h.teamsWhenAreIdentical(row, oldRow, 'Join')
end
return false
end
function h.teamsWhenAreIdentical(row, oldRow, when)
for _, v in ipairs({ 'Team', 'RoleModifier', 'Role', 'Status' }) do
if row[v .. when] ~= oldRow[v .. when] then
return false
end
end
return true
end
function p:doWeNeedANewLine(changesByLine, row, index)
if not row.TeamLeave then return true end
return not index
end
function p:getOldChangeFromIndex(changesByLine, oldChangeLineKey)
return changesByLine[oldChangeLineKey.changeNumber]
end
function p:getMostRecentLineNumberFromIndex(index)
return index[#index].changeNumber
end
function p:updateIndex(changesByLine, row, index)
index[#index+1] = { changeNumber = #changesByLine }
end
function p:addNewTeamToExistingLine(line, row)
-- we earlier split stuff into separate start/end so we should be fine to just merge now
if line.TeamLeave then return end
self:super('addNewTeamToExistingLine', line, row)
end
-- formatting
function p:formatRowForOutput(row)
row.PlayerLinked = util_esports.playerLinked(row.Player)
row.RoleDisplay = row.Roles:images{ sep="", size=15 }
row.TeamDisplay = self:getTeamDisplay(row)
row.RegionDisplay = row.Region:image()
row.DateLeaveDisplay = self:getDateDisplay(row, 'Leave')
row.DateJoinDisplay = self:getDateDisplay(row, 'Join')
row.DurationDisplay = util_timedelta.approxDurationDisplay(self:getDurationArgs(row))
self:getStatusAndUpdateFlag(row)
self:addSortKeys(row)
h.correctMissingDates(row)
end
function p:getTeamDisplay(row) end
function p:getDateDisplay(row, when) end
function p:getDurationArgs(row)
local tbl = {
row.DateJoin or 'xxxx-xx-xx',
row.DateLeave or os.date('%Y-%m-%d'),
row.IsApproxDateJoin or row.IsApproxDateLeave,
}
return unpack(tbl)
end
function p:getStatusAndUpdateFlag(row) end
function h.correctMissingDates(row)
if not row.DateJoinDisplay then
row.DateJoinDisplay = '??? ????'
h.addSortKey(row, 'DateJoinDisplay', -1)
end
if not row.DateLeaveDisplay then
row.DateLeaveDisplay = ("''%s''"):format(i18n.print('present'))
h.addSortKey(row, 'DateLeaveDisplay', 9999999999999)
end
end
function p:addSortKeys(row)
h.addSortKey(row, 'DateJoinDisplay', row.index)
h.addSortKey(row, 'TeamDisplay', row.Team)
h.addSortKey(
row,
'DateLeaveDisplay',
util_time.unix(row.Date_SortLeave)
)
h.addSortKey(
row,
'DurationDisplay',
util_timedelta.approxDurationSeconds(self:getDurationArgs(row))
)
end
function h.addSortKey(row, col, sortkey)
if not row[col] then return end
row[col .. '_Sort'] = sortkey
end
function p:setEarlierRowNextTeamValues(changesByLine, queryRow)
if queryRow.Direction == 'Leave' then return end
for _, outputRow in ipairs(changesByLine) do
if not outputRow.TeamLeave then
-- we can't add a NextTeam if they're still on the team
else
outputRow.NextTeam = outputRow.NextTeam or queryRow.Team
outputRow.changeIndexNext = 'queryRow.index'
end
end
end
-- cargo STORE
function p:storeCargo(changesByLine) end
-- output
function p:makeOutput(changesByLine)
self:setColumnsBasedOnIncludedInformation()
local output = mw.html.create()
local tbl = output:tag('table')
:addClass('player-team-history')
:addClass('hoverable-rows')
:addClass('sortable')
self:printHeader(tbl)
util_map.selfRowsInPlace(self, changesByLine, p.printOneRow, tbl)
return output
end
function p:printHeader(tbl)
local tr = tbl:tag('tr')
for _, col in ipairs(self.COLUMNS) do
tr:tag('th')
:wikitext(i18n.print(col))
:attr('data-sort-type', self.COLUMNS.sorttypes[col])
:addClass(self.COLUMNS.colclasses[col])
end
end
function p:setColumnsBasedOnIncludedInformation()
if self.INCLUDE_STATUS then
self.COLUMNS[#self.COLUMNS+1] = 'StatusDisplay'
end
self.COLUMNS.classes[self.COLUMNS[#self.COLUMNS]] = 'roster-portal-lastcell'
end
function p:printOneRow(row, tbl)
local tr = tbl:tag('tr')
for _, col in ipairs(self.COLUMNS) do
self:printOneCell(tr, row, col, self.COLUMNS)
end
self:printEditButtons(util_html.lastChild(tr), row)
end
function p:printOneCell(tr, row, col)
tr:tag('td')
:wikitext(row[col])
:addClass(self.COLUMNS.classes[col])
:attr('data-sort-value', row[col .. '_Sort'])
end
function p:printEditButtons(td, row) end
return p