Add new combined election type
This type is for when TC and PTL elections must overlap. There is a built in assumption that each "phase" of an election: - Nominations - Campaigning - Election are completely synchronous NOTE: This change leaves the template-emails broken, as I do not add new functions for combined_$email. This is because I'd like to If64c075b4a799574bc51ccd13019d472ee9a4b0e merge first, and I'd also like to avoid to many rebases Change-Id: Id39c50e71dc91bea8034ce443287463094e2f37f
This commit is contained in:
parent
30ddc77e97
commit
c6406fdbd5
1
.gitignore
vendored
1
.gitignore
vendored
@ -15,6 +15,7 @@ doc/source/archive_toc.rst
|
||||
doc/source/*/*.rst
|
||||
doc/source/ptl.rst
|
||||
doc/source/tc.rst
|
||||
doc/source/combined.rst
|
||||
doc/source/events.rst
|
||||
doc/source/configuration.rst
|
||||
doc/source/results/*/announce_ptl.rst
|
||||
|
@ -99,10 +99,8 @@ def build_lists(app):
|
||||
if utils.election_is_running():
|
||||
# Current candidates
|
||||
candidates_list = utils.build_candidates_list()
|
||||
if not utils.is_tc_election():
|
||||
render_list("ptl", candidates_list)
|
||||
else:
|
||||
render_list("tc", candidates_list)
|
||||
election_type = utils.conf.get('election_type', '').lower()
|
||||
render_list(election_type, candidates_list)
|
||||
|
||||
# Archived elections
|
||||
previous_toc = [
|
||||
@ -127,13 +125,9 @@ class CandidatesDirective(Directive):
|
||||
def run(self):
|
||||
if not utils.election_is_running():
|
||||
return []
|
||||
election_type = utils.conf.get('election_type', '').lower()
|
||||
|
||||
rst = '.. include:: '
|
||||
if utils.is_tc_election():
|
||||
rst += 'tc.rst'
|
||||
else:
|
||||
rst += 'ptl.rst'
|
||||
|
||||
rst = '.. include:: %s.rst' % (election_type)
|
||||
result = ViewList()
|
||||
for idx, line in enumerate(rst.splitlines()):
|
||||
result.append(line, 'CandidatesDirective', idx)
|
||||
|
17
doc/source/_exts/combined.jinja
Normal file
17
doc/source/_exts/combined.jinja
Normal file
@ -0,0 +1,17 @@
|
||||
{{ election.capitalize() }} TC Candidates
|
||||
======================
|
||||
|
||||
{% for candidate in candidates['TC'] %}
|
||||
* `{{ candidate['fullname'] }} {% if candidate['ircname'] is not none %}({{ candidate['ircname'] }}){% endif %} <{{ candidate['url'] }}>`__
|
||||
{% endfor %}
|
||||
|
||||
{{ election.capitalize() }} PTL Candidates
|
||||
======================
|
||||
{% for project in projects|sort %}{% if project != 'TC' %}
|
||||
* {{ project.replace('_', ' ') }}
|
||||
|
||||
{% for candidate in candidates[project] %}
|
||||
* `{{ candidate['fullname'] }} {% if candidate['ircname'] is not none %}({{ candidate['ircname'] }}){% endif %} <{{ candidate['url'] }}>`__
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}{% endfor %}
|
@ -55,6 +55,7 @@ def main():
|
||||
|
||||
args = parser.parse_args()
|
||||
projects = utils.get_projects(tag=args.tag, fallback_to_master=True)
|
||||
election_type = utils.conf.get('election_type', '').lower()
|
||||
|
||||
for review in get_reviews():
|
||||
if review['status'] != 'NEW':
|
||||
@ -77,7 +78,10 @@ def main():
|
||||
candiate_ok = checks.validate_member(filepath)
|
||||
|
||||
if candiate_ok:
|
||||
if not utils.is_tc_election():
|
||||
# If we're a PTL election OR if the team is not TC we need
|
||||
# to check for validating changes
|
||||
if (election_type == 'ptl'
|
||||
or (election_type == 'combined' and team != 'TC')):
|
||||
if args.interactive:
|
||||
print('The following commit and profile validate this '
|
||||
'candidate:')
|
||||
|
@ -125,6 +125,7 @@ def main():
|
||||
return 1
|
||||
|
||||
projects = utils.get_projects(tag=args.tag, fallback_to_master=True)
|
||||
election_type = utils.conf.get('election_type', '').lower()
|
||||
|
||||
if args.files:
|
||||
to_process = args.files
|
||||
@ -134,13 +135,24 @@ def main():
|
||||
to_process = utils.find_candidate_files(election=args.release)
|
||||
|
||||
for filepath in to_process:
|
||||
email = utils.get_email(filepath)
|
||||
team = os.path.basename(os.path.dirname(filepath))
|
||||
|
||||
# Some kind souls remove the .placeholder file when they upload
|
||||
# a candidacy
|
||||
if email == '.placeholder':
|
||||
continue
|
||||
|
||||
candidate_ok = True
|
||||
|
||||
candidate_ok &= validate_filename(filepath)
|
||||
candidate_ok &= validate_member(filepath)
|
||||
|
||||
if candidate_ok and not utils.is_tc_election():
|
||||
candidate_ok &= check_for_changes(projects, filepath, args.limit)
|
||||
if candidate_ok:
|
||||
if (election_type == 'ptl'
|
||||
or (election_type == 'combined' and team != 'TC')):
|
||||
candidate_ok &= check_for_changes(projects, filepath,
|
||||
args.limit)
|
||||
|
||||
errors |= not candidate_ok
|
||||
|
||||
|
@ -133,6 +133,9 @@ def main():
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# NOTE(tonyb): If we're a "combined" election we'll have the required
|
||||
# events for the statistics to render collectly so we can just use a quick
|
||||
# 'is_tc' check here
|
||||
if utils.is_tc_election():
|
||||
print('This tool only works for PTL elections not TC')
|
||||
return 0
|
||||
|
@ -31,7 +31,9 @@ fmt_args = dict(
|
||||
start_release=start_release,
|
||||
time_frame=time_frame,
|
||||
)
|
||||
if utils.is_tc_election():
|
||||
|
||||
election_type = utils.conf.get('election_type', '').lower()
|
||||
if election_type in ['tc', 'combined']:
|
||||
fmt_args.update(dict(
|
||||
start_nominations=utils.get_event('TC Nominations')['start_str'],
|
||||
end_nominations=utils.get_event('TC Nominations')['end_str'],
|
||||
@ -42,7 +44,11 @@ if utils.is_tc_election():
|
||||
poll_name='%s TC Election' % (conf['release'].capitalize()),
|
||||
))
|
||||
template_names += ['campaigning_kickoff']
|
||||
else:
|
||||
|
||||
# NOTE(tonyb): In the case of a "combined" election we assume that the dates
|
||||
# for each "phase" (nominations, campaigning or elections) overlap so updating
|
||||
# the end_nominations key here with the PTL date should be safe
|
||||
if election_type in ['ptl', 'combined']:
|
||||
# NOTE(tonyb): We need an empty item last to ensure the path ends in a
|
||||
# tailing '/'
|
||||
stats.collect_project_stats(os.path.join(utils.CANDIDATE_PATH,
|
||||
|
@ -41,26 +41,35 @@ class TestGerritUtils(base.ElectionTestCase):
|
||||
|
||||
|
||||
class TestFindCandidateFiles(base.ElectionTestCase):
|
||||
@mock.patch.object(utils, 'is_tc_election', return_value=False)
|
||||
@mock.patch('os.path.exists', return_value=True)
|
||||
@mock.patch('os.listdir', side_effect=[['SomeProject', 'TC'],
|
||||
['invalid@example.com']])
|
||||
def test_ptl_lists(self, mock_listdir, mock_path_exists,
|
||||
mock_is_tc_election):
|
||||
candidate_files = utils.find_candidate_files(election='fake')
|
||||
def test_ptl_lists(self, mock_listdir, mock_path_exists):
|
||||
with mock.patch.dict(utils.conf, dict(election_type='ptl')):
|
||||
candidate_files = utils.find_candidate_files(election='fake')
|
||||
self.assertEqual(['candidates/fake/SomeProject/invalid@example.com'],
|
||||
candidate_files)
|
||||
|
||||
@mock.patch.object(utils, 'is_tc_election', return_value=True)
|
||||
@mock.patch('os.path.exists', return_value=True)
|
||||
@mock.patch('os.listdir', side_effect=[['SomeProject', 'TC'],
|
||||
['invalid@example.com']])
|
||||
def test_tc_lists(self, mock_listdir, mock_path_exists,
|
||||
mock_is_tc_election):
|
||||
candidate_files = utils.find_candidate_files(election='fake')
|
||||
def test_tc_lists(self, mock_listdir, mock_path_exists):
|
||||
with mock.patch.dict(utils.conf, dict(election_type='tc')):
|
||||
candidate_files = utils.find_candidate_files(election='fake')
|
||||
self.assertEqual(['candidates/fake/TC/invalid@example.com'],
|
||||
candidate_files)
|
||||
|
||||
@mock.patch('os.path.exists', return_value=True)
|
||||
@mock.patch('os.listdir', side_effect=[['SomeProject', 'TC'],
|
||||
['invalid@example.com'],
|
||||
['invalid@example.com']])
|
||||
def test_combined_lists(self, mock_listdir, mock_path_exists):
|
||||
with mock.patch.dict(utils.conf, dict(election_type='combined')):
|
||||
candidate_files = utils.find_candidate_files(election='fake')
|
||||
self.assertEqual(['candidates/fake/SomeProject/invalid@example.com',
|
||||
'candidates/fake/TC/invalid@example.com'],
|
||||
candidate_files)
|
||||
|
||||
|
||||
class TestBuildCandidatesList(base.ElectionTestCase):
|
||||
@mock.patch.object(utils, 'lookup_member')
|
||||
|
@ -286,17 +286,18 @@ def election_is_running():
|
||||
|
||||
def find_candidate_files(election=conf['release']):
|
||||
election_path = os.path.join(CANDIDATE_PATH, election)
|
||||
election_type = conf.get('election_type', '').lower()
|
||||
if os.path.exists(election_path):
|
||||
project_list = os.listdir(election_path)
|
||||
else:
|
||||
project_list = []
|
||||
|
||||
if is_tc_election():
|
||||
if election_type == 'tc':
|
||||
project_list = list(filter(
|
||||
lambda p: p in ['TC'],
|
||||
project_list
|
||||
))
|
||||
else:
|
||||
elif election_type == 'ptl':
|
||||
project_list = list(filter(
|
||||
lambda p: p not in ['TC'],
|
||||
project_list
|
||||
|
Loading…
Reference in New Issue
Block a user