diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8dada3e..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md deleted file mode 100644 index 0973817..0000000 --- a/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# uc-recognition -This repository contains scripts and useful references to track contributions to OpenStack by users - -Find active moderators on Ask OpenStack: -* get_active_moderator.py - -Uses IRC logs to attempt to determine active working group members: -* get_meeting_data.sh -* get_active_wg_members.py - diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..4ee2c5f --- /dev/null +++ b/README.rst @@ -0,0 +1,10 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For any further questions, please email +openstack-discuss@lists.openstack.org or join #openstack-dev on +OFTC. diff --git a/tools/get_active_commiters.py b/tools/get_active_commiters.py deleted file mode 100755 index 3694877..0000000 --- a/tools/get_active_commiters.py +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env python - -# Copyright (C) 2013-2014 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Soren Hansen wrote the original version of this script. -# James Blair hacked it up to include email addresses from gerrit. -# Jeremy Stanley overhauled it for gerrit 2.8 and our governance repo. -# Tom Fifield cut it to pieces as a quick hack for the UC recognition to be -# replaced with something nicer at the earliest possible time - -import datetime -import json -import optparse -import os -import os.path -import re -import io - -import paramiko - -MAILTO_RE = re.compile('mailto:(.*)') -USERNAME_RE = re.compile('username:(.*)') - -class Account(object): - def __init__(self, num): - self.num = num - self.full_name = '' - self.emails = [] - self.username = None - - -def get_account(accounts, num): - a = accounts.get(num) - if not a: - a = Account(num) - accounts[num] = a - return a - - -def repo_stats(repo, output, begin, end, keyfile, user): - username_accounts = {} - atcs = [] - - QUERY = "project:%s status:merged" % repo - - client = paramiko.SSHClient() - client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - client.load_system_host_keys() - client.connect( - 'review.openstack.org', port=29418, - key_filename=os.path.expanduser(keyfile), username=user) - stdin, stdout, stderr = client.exec_command( - 'gerrit query %s --all-approvals --format JSON' % QUERY) - - done = False - last_sortkey = '' - begin_time = datetime.datetime( - int(begin[0:4]), int(begin[4:6]), int(begin[6:8]), - int(begin[8:10]), int(begin[10:12]), int(begin[12:14])) - end_time = datetime.datetime( - int(end[0:4]), int(end[4:6]), int(end[6:8]), - int(end[8:10]), int(end[10:12]), int(end[12:14])) - - count = 0 - earliest = datetime.datetime.now() - while not done: - for l in stdout: - data = json.loads(l) - if 'rowCount' in data: - if data['rowCount'] < 500: - done = True - continue - count += 1 - if 'sortKey' in data.keys(): - last_sortkey = data['sortKey'] - if 'owner' not in data: - continue - if 'username' not in data['owner']: - continue - account = Account(None) - account.username = data['owner']['username'] - account.emails = [data['owner']['email']] - account.full_name = data['owner']['name'] - approved = False - for ps in data['patchSets']: - if 'approvals' not in ps: - continue - for aprv in ps['approvals']: - if aprv['type'] != 'SUBM': - continue - ts = datetime.datetime.fromtimestamp(aprv['grantedOn']) - if ts < begin_time or ts > end_time: - continue - approved = True - if ts < earliest: - earliest = ts - if approved and account not in atcs: - atcs.append(account) - if not done: - stdin, stdout, stderr = client.exec_command( - 'gerrit query %s resume_sortkey:%s --all-approvals' - ' --format JSON' % (QUERY, last_sortkey)) - - print 'repo: %s' % repo - print 'examined %s changes' % count - print 'earliest timestamp: %s' % earliest - output_file = io.open(output, 'w', encoding='UTF-8') - for a in atcs: - output_file.write(a.username + ","+ a.full_name + "," + a.emails[0] + "\n") - output_file.close() - - -def main(): - now = ''.join( - '%02d' % x for x in datetime.datetime.utcnow().utctimetuple()[:6]) - - optparser = optparse.OptionParser() - optparser.add_option( - '-b', '--begin', help='begin date/time (e.g. 20131017000000)') - optparser.add_option( - '-e', '--end', default=now, help='end date/time (default is now)') - optparser.add_option( - '-k', '--keyfile', default='~/.ssh/id_rsa', - help='SSH key (default is ~/.ssh/id_rsa)') - optparser.add_option( - '-u', '--user', default=os.environ['USER'], - help='SSH username (default is $USER)') - options, args = optparser.parse_args() - - projects = ['openstack/ops-tags-team', - 'openstack/osops-tools-monitoring', - 'openstack/osops-tools-generic', - 'openstack/osops-example-configs', - 'openstack/osops-tools-logging', - 'openstack/osops-tools-contrib', - 'openstack/openstack-user-stories', - 'openstack/uc-recognition', - 'openstack/osops-coda'] - - for repo in projects: - output = '%s.csv' % repo.split('/')[-1] - repo_stats(repo, output, options.begin, options.end, - options.keyfile, options.user) - - -if __name__ == "__main__": - main() diff --git a/tools/get_active_moderator.py b/tools/get_active_moderator.py deleted file mode 100644 index 47edee7..0000000 --- a/tools/get_active_moderator.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2016 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import json -import requests -import time - -user_list = 'https://ask.openstack.org/en/api/v1/users/' - -params = dict( - sort='reputation', - page=1 -) - - -def get_user_data(karma_level): - """ - Loop through the user list to find users that have greater karma than - karma level. - Returns a list of user data dicts. - """ - page = 1 - session = requests.Session() - response = session.get(user_list, params=params) - user_data = json.loads(response.text)['users'] - while user_data[-1]['reputation'] >= karma_level: - page = page + 1 - params.update({'page': page}) - print "Getting page: %d" % page - response = session.get(user_list, params=params) - user_data.extend(json.loads(response.text)['users']) - time.sleep(3) - - # since pages are big chunks, we will have some users that are - # having karma lower than karma_level in the last page. Remove them. - while user_data[-1]['reputation'] < karma_level: - user_data.pop() - - return user_data - - -def get_active_users(user_data, last_active_days=180): - """ - Give a list of user dict objects, return the ones that - were active within the number of days specificed by - last_active days. - Prints a list of usernames, reputations and IDs - """ - - now = datetime.datetime.now() - active_threshold = now - datetime.timedelta(days=last_active_days) - for user in user_data: - last_seen_at = datetime.datetime.fromtimestamp( - int(user['last_seen_at'])) - if last_seen_at > active_threshold: - print "{: <20} {: <20}".format(user['username'], str(user['id'])) - - -def main(): - user_data = get_user_data(karma_level=200) - get_active_users(user_data, last_active_days=180) - - -if __name__ == "__main__": - main() diff --git a/tools/get_active_wg_members.py b/tools/get_active_wg_members.py deleted file mode 100644 index 56a3af8..0000000 --- a/tools/get_active_wg_members.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2016 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from datetime import datetime -from datetime import timedelta -import operator -import optparse -import os - -meeting_mappings = { -'uc': 'user_committee', -'product_team': 'product_working_group', -'large_deployments_team_monthly_meeting': 'large_deployment_team', -'ops_meetup_team': 'ops_meetups_team', -'operators_telco_nfv': 'telcowg', -} - - -def get_recent_meets(log_dir, last_active_days=180): - """ - takes a directory heirachy that only contains meetbot - txt summary files, determines the users active within - the threshold. Returns a dictionary that has - one entry per meeting category, containing information about - who attended which meetings and how much they said. - """ - meetings = {} - now = datetime.now() - active_threshold = now - timedelta(days=last_active_days) - - # get our list of meetings and timestamps - for root, dirs, files in os.walk(log_dir): - if len(files) > 0: - for txt_summary in files: - (meet_name, meet_date) = txt_summary.split('.', 1) - meet_date = meet_date[0:-4] # drop the .txt at the end - if meet_name in meeting_mappings.keys(): - meet_name = meeting_mappings[meet_name] - meet_timestamp = datetime.strptime(meet_date, "%Y-%m-%d-%H.%M") - if meet_timestamp > active_threshold: - if meet_name not in meetings.keys(): - meetings[meet_name] = [] - meet_file = root + "/" + txt_summary - meetings[meet_name].append(get_people_in_meeting(meet_file)) - - return meetings - - -def get_people_in_meeting(meeting_txt): - """ - takes a meetbot summary file that has a section with the following format - and returns a dict with username<->lines said mapping - - People present (lines said) - --------------------------- - - * username (117) - * username2 (50) - """ - meeting_people = [] - in_people = False - txt_file = open(meeting_txt) - for line in txt_file: - if line == "People present (lines said)\n": - in_people = True - elif not in_people: - next - elif in_people and '*' not in line: - next - elif in_people and 'openstack' not in line: - ircnic, linessaid = line[2:-2].split('(') - ircnic = ircnic.strip(" _").lower() - meeting_people.append((ircnic, linessaid)) - - txt_file.close() - return meeting_people - - -def get_meeting_aggregates(meeting_data): - """ - Aggregates the attendance counts and lines said for users across - a meeting category - """ - meeting_aggregate = {} - for meeting_name in meeting_data.keys(): - meeting_users = {} - for meeting in meeting_data[meeting_name]: - for user_tuple in meeting: - if user_tuple[0] not in meeting_users.keys(): - meeting_users[user_tuple[0]] = {'attendance_count': 1, - 'lines_said': int(user_tuple[1])} - else: - meeting_users[user_tuple[0]]["attendance_count"] += 1 - meeting_users[user_tuple[0]]["lines_said"] += int(user_tuple[1]) - meeting_aggregate[meeting_name] = meeting_users - return meeting_aggregate - - -def print_meet_stats(meeting_data): - for meeting_name in meeting_data.keys(): - print "\n" + meeting_name + "\n=====================================\n" - sorted_users = sorted(meeting_data[meeting_name].items(), reverse=True, - key=operator.itemgetter(1)) - for user in sorted_users: - print "{: <20} {: <20} {: <20}".format(user[0], - user[1]["attendance_count"], - user[1]["lines_said"]) - - -def print_eligible_usernames(meeting_data, num_meetings=1, lines_said=1, human=False): - user_aggregate = {} - for meeting_name in meeting_data.keys(): - for user_tuple in meeting_data[meeting_name].items(): - if user_tuple[0] not in user_aggregate.keys(): - user_aggregate[user_tuple[0]] = user_tuple[1] - else: - user_aggregate[user_tuple[0]]["lines_said"] += user_tuple[1]["lines_said"] - user_aggregate[user_tuple[0]]["attendance_count"] += user_tuple[1]["attendance_count"] - if human: - print "\n OVERALL STATS \n=====================================\n" - sorted_users = sorted(user_aggregate.items(), reverse=True, - key=operator.itemgetter(1)) - for user in sorted_users: - if user[1]["attendance_count"] >= num_meetings or user[1]["lines_said"] >= lines_said: - if human: - print "{: <20} {: <20} {: <20}".format(user[0], - user[1]["attendance_count"], - user[1]["lines_said"]) - else: - print "{},{},{}".format(user[0], - user[1]["attendance_count"], - user[1]["lines_said"]) - - -def main(): - optparser = optparse.OptionParser() - optparser.add_option( - '--human', help='If set, output results in human-readable format', - default=False, action="store_true") - optparser.add_option( - '-d', '--datadir', help='Where meeting data lives', - default='./eavesdrop.openstack.org/meetings') - optparser.add_option( - '-t', '--days', help='Validity of attendance in days', - type="int", default=183) - optparser.add_option( - '-n', '--nummeetings', help='Required number of meetings', - type="int", default=2) - optparser.add_option( - '-l', '--linessaid', help='Required number of line said', - type="int", default=10) - options, args = optparser.parse_args() - - meeting_data = get_recent_meets(options.datadir, options.days) - meeting_aggregate = get_meeting_aggregates(meeting_data) - if options.human: - print_meet_stats(meeting_aggregate) - print_eligible_usernames(meeting_aggregate, options.nummeetings, - options.linessaid, options.human) - -if __name__ == "__main__": - main() diff --git a/tools/get_meeting_data.sh b/tools/get_meeting_data.sh deleted file mode 100755 index 429907b..0000000 --- a/tools/get_meeting_data.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -# -# Downloads the TXT summary file from the meetbot records -# at OpenStack for specific meetings - -MEETINGS="operators_ops_tools_monitoring ops_tags _operator_tags" -MEETINGS="$MEETINGS large_deployment_team large_deployments_team" -MEETINGS="$MEETINGS large_deployments_team_monthly_meeting" -MEETINGS="$MEETINGS log_wg openstack_operators ops_meetups_team ops_meetup_team" -MEETINGS="$MEETINGS product_team product_work_group product_working_group" -MEETINGS="$MEETINGS scientific_wg telcowg uc user_committee wos_mentoring" -MEETINGS="$MEETINGS massively_distributed_clouds operators_telco_nfv" - -for meeting in $MEETINGS -do - wget --no-parent --recursive --accept "*.txt" --reject="*.log.txt" http://eavesdrop.openstack.org/meetings/$meeting/ -done