From ad35d60dc767a93ccadd60be14746b4b10104a85 Mon Sep 17 00:00:00 2001 From: Ryan Gard Date: Mon, 17 Oct 2016 09:46:34 -0700 Subject: [PATCH 1/2] (QA-2667) Update "parse_status" to Track Maintenance The tool has been updated to track "maintenance" versus "feature" work. This required a format change for the team member YAML status reports. Also, the tool was updated to report which team member YAML status reports are empty. --- tools/parse_status/README.md | 54 +++++---- tools/parse_status/parse_status_app.py | 105 +++++++++++------- .../{andy_hayes.yaml => Andy Hayes.yaml} | 0 .../{chris_cowell.yaml => Chris Cowell.yaml} | 0 ...pher_thorn.yaml => Christopher Thorn.yaml} | 0 ...{eric_thompson.yaml => Eric Thompson.yaml} | 0 .../{erick_banks.yaml => Erick Banks.yaml} | 0 .../{erik_dasher.yaml => Erick Dasher.yaml} | 0 .../{james_stocks.yaml => James Stocks.yaml} | 0 .../{john_duarte.yaml => John Duarte.yaml} | 0 .../{kurt_wall.yaml => Kurt Wall.yaml} | 0 .../{lucy_wyman.yaml => Lucy Wyman.yaml} | 0 .../{mark_wilson.yaml => Mark Wilson.yaml} | 0 .../{paula_mc-maw.yaml => Paula McMaw.yaml} | 0 .../{phong_ly.yaml => Phong Ly.yaml} | 0 .../{sam_woods.yaml => Sam Woods.yaml} | 0 ...amus_mc-kenna.yaml => Seamus McKenna.yaml} | 0 .../{sean_griffin.yaml => Sean Griffin.yaml} | 0 ...harakanparampil.yaml => Shaigy Nixon.yaml} | 0 ...uffy.yaml => Sheena Tharakanparampil.yaml} | 0 ...wilson_mc-coubrey.yaml => Stan Duffy.yaml} | 0 ...ch_reichert.yaml => Wilson McCoubrey.yaml} | 0 .../prototypes/Zach Reichert.yaml | 0 23 files changed, 99 insertions(+), 60 deletions(-) rename tools/parse_status/status_reports/prototypes/{andy_hayes.yaml => Andy Hayes.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{chris_cowell.yaml => Chris Cowell.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{christopher_thorn.yaml => Christopher Thorn.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{eric_thompson.yaml => Eric Thompson.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{erick_banks.yaml => Erick Banks.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{erik_dasher.yaml => Erick Dasher.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{james_stocks.yaml => James Stocks.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{john_duarte.yaml => John Duarte.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{kurt_wall.yaml => Kurt Wall.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{lucy_wyman.yaml => Lucy Wyman.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{mark_wilson.yaml => Mark Wilson.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{paula_mc-maw.yaml => Paula McMaw.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{phong_ly.yaml => Phong Ly.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{sam_woods.yaml => Sam Woods.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{seamus_mc-kenna.yaml => Seamus McKenna.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{sean_griffin.yaml => Sean Griffin.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{sheena_tharakanparampil.yaml => Shaigy Nixon.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{stan_duffy.yaml => Sheena Tharakanparampil.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{wilson_mc-coubrey.yaml => Stan Duffy.yaml} (100%) rename tools/parse_status/status_reports/prototypes/{zach_reichert.yaml => Wilson McCoubrey.yaml} (100%) create mode 100644 tools/parse_status/status_reports/prototypes/Zach Reichert.yaml diff --git a/tools/parse_status/README.md b/tools/parse_status/README.md index 35052679..4630167f 100644 --- a/tools/parse_status/README.md +++ b/tools/parse_status/README.md @@ -47,40 +47,54 @@ The YAML file contents should look like the following: ```yaml # Project Commitments --- -Alpha: { - commitment: 4 -} -Beta: { +- + project: Alpha + commitment: 2 + maintenance: no +- + project: Alpha + commitment: 2 + maintenance: yes + comment: "Addressed some old tech debt for the project." +- + project: Beta commitment: 1 -} -Charlie: { + maintenance: yes +- + project: Charlie commitment: 1 -} -Delta: { + maintenance: yes +- + project: Delta commitment: 0, + maintenance: no comment: "Blocked because of outside vendor" -} -Other: { - commitment: 2, +- + project: Other + commitment: 2 + maintenance: no comment: "Writing proposal for PuppetConf" -} -Consulting: { - commitment: 2, +- + project: Consulting + commitment: 2 + # Unspecified "maintenance" key is interpreted as "maintenance: no" comment: "Mentoring new hire." -} ``` **Content Constraints** -- Do not repeat the same project multiple times. - * If you perform distinct activities within a project use the "comment" key to provide more details. -- Each project can only have one "commitment" key and one "comment" key. +- If you perform distinct activities within a project use the "comment" key to provide more details. +- Set the "maintenance" key to "yes" to indicate that the work was maintenance related. + * Setting the "maintenance" key to "no" implies that the work was feature related. + * Excluding the "maintenance" key implies that the work was feature related. + * A single project can have multiple entries. +- Each project entry can only have one "commitment", "maintenance" or "comment" key. ### YAML File Naming The `parse_status_app.py` utility expects a directory with YAML files. The YAML file names need to -follow the format `FIRST_LAST.yaml`. For Irish last names with prefixes use the format -`FIRST_PREFIX-LAST.yaml`. +follow the format ` .yaml` using appropriate casing. For example a report YAML file +name for someone with an Irish last name would look like `Paula McMaw.yaml`. ## Workflow diff --git a/tools/parse_status/parse_status_app.py b/tools/parse_status/parse_status_app.py index 2e43de59..189b85d1 100644 --- a/tools/parse_status/parse_status_app.py +++ b/tools/parse_status/parse_status_app.py @@ -15,11 +15,10 @@ import io from argparse import ArgumentParser from os import listdir -from os.path import basename, join +from os.path import basename, join, splitext from fnmatch import filter from datetime import date from csv import writer -from re import match try: from yaml import load, YAMLError except ImportError: @@ -34,20 +33,23 @@ class ProjectCommitmentRecord(object): Args: project_name |str| = The name of the project. commitment_level |int| = An integer in the range of 0-10 representing commitment. + work_type |str| = The type of work for the given commitment. ("feature" or "maintenance") comment |str| = An optional free-form comment describing the commitment. Raises: |RuntimeError| = Provided commitment level is out of range. (0-10) """ - def __init__(self, project_name, commitment_level, comment=''): + def __init__(self, project_name, commitment_level, work_type, comment=''): self._project_name = project_name + self._work_type = work_type + self._comment = comment + if 0 <= commitment_level <= 10: self._commitment_level = commitment_level else: raise RuntimeError('Provided commitment level for the "{}" project ' 'is out of range!'.format(project_name)) - self._comment = comment @property def project_name(self): @@ -69,6 +71,16 @@ def commitment_level(self): return self._commitment_level + @property + def work_type(self): + """The type of work for the given commitment. ("feature" or "maintenance") + + Returns: + |str| + """ + + return self._work_type + @property def comment(self): """An optional free-form comment describing the commitment. @@ -96,12 +108,13 @@ def __init__(self, team_member, status_date): self._status_date = status_date self._commitments = [] #[ProjectCommitmentRecord] - def add_commitment_record(self, project_name, commitment_level, comment=''): + def add_commitment_record(self, project_name, commitment_level, work_type, comment=''): """Add a project commitment record for the given team member. Args: project_name |str| = The name of the team member associated with project commitments. - commitment_level |int| = An integer in the range of 0-5 representing commitment. + commitment_level |int| = An integer in the range of 0-10 representing commitment. + work_type |str| = The type of work for the given commitment. ("feature" or "maintenance") comment |str| = An optional free-form comment describing the commitment. Returns: @@ -112,9 +125,10 @@ def add_commitment_record(self, project_name, commitment_level, comment=''): """ try: - self._commitments.append(ProjectCommitmentRecord(project_name, commitment_level, comment)) + self._commitments.append( + ProjectCommitmentRecord(project_name, commitment_level, work_type, comment)) except RuntimeError as e: - raise RuntimeError('{} For the "{}" team member.'.format(e.msg, self._team_member)) + raise RuntimeError('{} for the "{}" team member.'.format(e.msg, self._team_member)) def validate(self): """Validate that the commitment level is 10 for the given team member. @@ -138,7 +152,12 @@ def commitment_status(self): |[[obj]]| """ - return [[self._team_member, self._status_date, x.project_name, x.commitment_level, x.comment] + return [[self._team_member, + self._status_date, + x.project_name, + x.commitment_level, + x.work_type, + x.comment] for x in self._commitments] @@ -160,6 +179,7 @@ def __init__(self, yaml_reports, report_output_path, report_date): self._report_output_path = report_output_path self._report_date = report_date self._status_database = [] # [ProjectCommitmentStatus] + self._empty_reports = [] # List of empty file report paths self._load_yaml_reports() @@ -173,30 +193,10 @@ def _get_team_member_name(self, yaml_file_path): |str| = A human readable team member name derived from the YAML file path. Raises: - |RuntimeError| = The YAML file name does not match the expected pattern. + |None| """ - first_last_pattern = r"([a-z]+)_([a-z\-]+).yaml" - irish_last_pattern = r"([a-z]+)\-([a-z]+)" - - try: - match_obj = match(first_last_pattern, basename(yaml_file_path)) - first_name = match_obj.group(1).capitalize() - last_name = match_obj.group(2) - - # Account for Irish last names. - if match(irish_last_pattern, last_name): - last_name = match(irish_last_pattern, last_name).group(1).capitalize() + \ - match(irish_last_pattern, last_name).group(2).capitalize() - else: - last_name = last_name.capitalize() - - team_member_name = "{} {}".format(first_name, last_name) - except (IndexError, AttributeError): - raise RuntimeError('The YAML file name "{}" does not match' - ' the expected pattern!'.format(yaml_file_path)) - - return team_member_name + return splitext(basename(yaml_file_path))[0] def _fix_smart_quotes(self, input_string): """Coerce smart quotes into straight quotes. Do nothing if smart quotes are not detected. @@ -213,11 +213,12 @@ def _fix_smart_quotes(self, input_string): return input_string.replace(u'\u201c','"').replace(u'\u201d','"') - def _import_commitment_status(self, team_member, info_dict): + def _import_commitment_status(self, team_member, info_list): """Import project commitment records for a given user. Args: - info_dict |{str:str}| = A nested dictionary containing commitment records. + team_member |str| = The team member name to collect status for. + info_list |[{str:str}]| = A list of dictionaries containing commitment records. Returns: |None| @@ -230,11 +231,19 @@ def _import_commitment_status(self, team_member, info_dict): status = ProjectCommitmentStatus(team_member, self._report_date) try: - for project in info_dict: - commitment_level = int(info_dict[project]['commitment']) - comment = info_dict[project]['comment'] if 'comment' in info_dict[project] else "" - - status.add_commitment_record(project, commitment_level, comment) + for record in info_list: + project = record['project'] + commitment_level = int(record['commitment']) + comment = record['comment'] if 'comment' in record else "" + + if 'maintenance' in record: + if type(record['maintenance']) is not bool: + raise RuntimeError("The 'maintenance' key must be either 'yes' or 'no'!") + work_type = 'maintenance' if record['maintenance'] else 'feature' + else: + work_type = 'feature' + + status.add_commitment_record(project, commitment_level, work_type, comment) except KeyError: raise RuntimeError('The YAML report does not follow expected format!') @@ -264,8 +273,9 @@ def _load_yaml_reports(self): with io.open(yaml_report, 'r', encoding='utf8') as yamlfile: yamlfile_contents = self._fix_smart_quotes(yamlfile.read()) - # Ignore empty files. - if yamlfile_contents == '': + # Record and ignore empty files. + if not yamlfile_contents: + self._empty_reports.append(yaml_report) continue self._import_commitment_status(self._get_team_member_name(yaml_report), @@ -295,6 +305,16 @@ def generate_csv_report(self): except (IOError, OSError): raise RuntimeError('Failed to write report to disk!') + @property + def empty_reports(self): + """A list of paths to empty report files. + + Returns: + |[str]| + """ + + return self._empty_reports + #=================================================================================================== # Functions @@ -390,6 +410,11 @@ def main(argv): report.generate_csv_report() + print('\nEmpty reports:') + if not report.empty_reports: print(' None') + for empty_report_path in report.empty_reports: + print(" {}".format(empty_report_path)) + print('\nSuccess!') except RuntimeError as e: exit_code = 1 diff --git a/tools/parse_status/status_reports/prototypes/andy_hayes.yaml b/tools/parse_status/status_reports/prototypes/Andy Hayes.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/andy_hayes.yaml rename to tools/parse_status/status_reports/prototypes/Andy Hayes.yaml diff --git a/tools/parse_status/status_reports/prototypes/chris_cowell.yaml b/tools/parse_status/status_reports/prototypes/Chris Cowell.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/chris_cowell.yaml rename to tools/parse_status/status_reports/prototypes/Chris Cowell.yaml diff --git a/tools/parse_status/status_reports/prototypes/christopher_thorn.yaml b/tools/parse_status/status_reports/prototypes/Christopher Thorn.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/christopher_thorn.yaml rename to tools/parse_status/status_reports/prototypes/Christopher Thorn.yaml diff --git a/tools/parse_status/status_reports/prototypes/eric_thompson.yaml b/tools/parse_status/status_reports/prototypes/Eric Thompson.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/eric_thompson.yaml rename to tools/parse_status/status_reports/prototypes/Eric Thompson.yaml diff --git a/tools/parse_status/status_reports/prototypes/erick_banks.yaml b/tools/parse_status/status_reports/prototypes/Erick Banks.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/erick_banks.yaml rename to tools/parse_status/status_reports/prototypes/Erick Banks.yaml diff --git a/tools/parse_status/status_reports/prototypes/erik_dasher.yaml b/tools/parse_status/status_reports/prototypes/Erick Dasher.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/erik_dasher.yaml rename to tools/parse_status/status_reports/prototypes/Erick Dasher.yaml diff --git a/tools/parse_status/status_reports/prototypes/james_stocks.yaml b/tools/parse_status/status_reports/prototypes/James Stocks.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/james_stocks.yaml rename to tools/parse_status/status_reports/prototypes/James Stocks.yaml diff --git a/tools/parse_status/status_reports/prototypes/john_duarte.yaml b/tools/parse_status/status_reports/prototypes/John Duarte.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/john_duarte.yaml rename to tools/parse_status/status_reports/prototypes/John Duarte.yaml diff --git a/tools/parse_status/status_reports/prototypes/kurt_wall.yaml b/tools/parse_status/status_reports/prototypes/Kurt Wall.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/kurt_wall.yaml rename to tools/parse_status/status_reports/prototypes/Kurt Wall.yaml diff --git a/tools/parse_status/status_reports/prototypes/lucy_wyman.yaml b/tools/parse_status/status_reports/prototypes/Lucy Wyman.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/lucy_wyman.yaml rename to tools/parse_status/status_reports/prototypes/Lucy Wyman.yaml diff --git a/tools/parse_status/status_reports/prototypes/mark_wilson.yaml b/tools/parse_status/status_reports/prototypes/Mark Wilson.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/mark_wilson.yaml rename to tools/parse_status/status_reports/prototypes/Mark Wilson.yaml diff --git a/tools/parse_status/status_reports/prototypes/paula_mc-maw.yaml b/tools/parse_status/status_reports/prototypes/Paula McMaw.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/paula_mc-maw.yaml rename to tools/parse_status/status_reports/prototypes/Paula McMaw.yaml diff --git a/tools/parse_status/status_reports/prototypes/phong_ly.yaml b/tools/parse_status/status_reports/prototypes/Phong Ly.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/phong_ly.yaml rename to tools/parse_status/status_reports/prototypes/Phong Ly.yaml diff --git a/tools/parse_status/status_reports/prototypes/sam_woods.yaml b/tools/parse_status/status_reports/prototypes/Sam Woods.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/sam_woods.yaml rename to tools/parse_status/status_reports/prototypes/Sam Woods.yaml diff --git a/tools/parse_status/status_reports/prototypes/seamus_mc-kenna.yaml b/tools/parse_status/status_reports/prototypes/Seamus McKenna.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/seamus_mc-kenna.yaml rename to tools/parse_status/status_reports/prototypes/Seamus McKenna.yaml diff --git a/tools/parse_status/status_reports/prototypes/sean_griffin.yaml b/tools/parse_status/status_reports/prototypes/Sean Griffin.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/sean_griffin.yaml rename to tools/parse_status/status_reports/prototypes/Sean Griffin.yaml diff --git a/tools/parse_status/status_reports/prototypes/sheena_tharakanparampil.yaml b/tools/parse_status/status_reports/prototypes/Shaigy Nixon.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/sheena_tharakanparampil.yaml rename to tools/parse_status/status_reports/prototypes/Shaigy Nixon.yaml diff --git a/tools/parse_status/status_reports/prototypes/stan_duffy.yaml b/tools/parse_status/status_reports/prototypes/Sheena Tharakanparampil.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/stan_duffy.yaml rename to tools/parse_status/status_reports/prototypes/Sheena Tharakanparampil.yaml diff --git a/tools/parse_status/status_reports/prototypes/wilson_mc-coubrey.yaml b/tools/parse_status/status_reports/prototypes/Stan Duffy.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/wilson_mc-coubrey.yaml rename to tools/parse_status/status_reports/prototypes/Stan Duffy.yaml diff --git a/tools/parse_status/status_reports/prototypes/zach_reichert.yaml b/tools/parse_status/status_reports/prototypes/Wilson McCoubrey.yaml similarity index 100% rename from tools/parse_status/status_reports/prototypes/zach_reichert.yaml rename to tools/parse_status/status_reports/prototypes/Wilson McCoubrey.yaml diff --git a/tools/parse_status/status_reports/prototypes/Zach Reichert.yaml b/tools/parse_status/status_reports/prototypes/Zach Reichert.yaml new file mode 100644 index 00000000..e69de29b From 4da678f91eff4324765f84c0b891efb6cf7cd685 Mon Sep 17 00:00:00 2001 From: Ryan Gard Date: Mon, 17 Oct 2016 14:30:41 -0700 Subject: [PATCH 2/2] Fix Vague Comment --- tools/parse_status/parse_status_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/parse_status/parse_status_app.py b/tools/parse_status/parse_status_app.py index 189b85d1..dc3118d3 100644 --- a/tools/parse_status/parse_status_app.py +++ b/tools/parse_status/parse_status_app.py @@ -339,7 +339,7 @@ def get_yaml_files(yaml_dir): #=================================================================================================== -# Main +# Main & Command-line Parser #=================================================================================================== def parse_cli(argv): """Parse the command-line and validate user input.