├── .gitignore ├── .gitmodules ├── .openshift ├── action_hooks │ ├── build │ ├── deploy │ ├── post_deploy │ └── pre_build └── cron │ └── daily │ └── warm_detailpage_cache ├── HINTS.md ├── LICENSE.txt ├── README.md ├── data └── .gitkeep ├── libs └── .gitkeep ├── requirements.txt ├── scripts ├── inve ├── run_task ├── update_blogs ├── update_bug_trackers ├── update_irc_meetings ├── update_mailing_lists ├── update_vcs_repositories └── warm_detailpage_cache ├── setup.py └── wsgi ├── .gitignore ├── .htaccess ├── application ├── healthmeter ├── .gitignore ├── __init__.py ├── bloginfo │ ├── __init__.py │ ├── importers │ │ ├── __init__.py │ │ ├── common.py │ │ └── rss.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── update_blogs.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ └── models.py ├── bootstrap.sql ├── btinfo │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ │ └── initial_data.json │ ├── importers │ │ ├── __init__.py │ │ ├── bugzilla_importer.py │ │ ├── common.py │ │ ├── github_importer.py │ │ ├── jira_importer.py │ │ └── redmine_importer.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── setup_github.py │ │ │ └── update_bug_trackers.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_bugtracker_api_token.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── eventinfo │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ └── models.py ├── fields.py ├── hmeter_frontend │ ├── __init__.py │ ├── admin.py │ ├── context_processors.py │ ├── fixtures │ │ ├── initial_data.json │ │ └── initial_metrics.json │ ├── forms.py │ ├── handlers.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── calc_metric_scores.py │ │ │ ├── clear_cache.py │ │ │ ├── render_paths.py │ │ │ ├── render_project.py │ │ │ ├── trim_results.py │ │ │ ├── update_everything.py │ │ │ └── warm_detailpage_cache.py │ ├── managers.py │ ├── metrics │ │ ├── __init__.py │ │ ├── algorithms │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── bugs.py │ │ │ ├── events.py │ │ │ ├── infrastructure.py │ │ │ ├── mailing_list.py │ │ │ ├── mixins.py │ │ │ ├── normalizers.py │ │ │ ├── publicity.py │ │ │ ├── quality.py │ │ │ ├── release.py │ │ │ └── vcs.py │ │ ├── calc.py │ │ ├── constants.py │ │ └── lookup.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── renderers.py │ ├── static │ │ ├── chaoss-logo.png │ │ ├── css │ │ │ ├── about.css │ │ │ ├── all-projects.less │ │ │ ├── bootstrap │ │ │ ├── bootstrap-slider.css │ │ │ ├── business-units.less │ │ │ ├── common.less │ │ │ ├── project-compare.css │ │ │ ├── project-detail.less │ │ │ ├── project-index.less │ │ │ └── project-problems.css │ │ ├── jquery-treeview │ │ ├── js │ │ │ ├── all-projects-search.js │ │ │ ├── bootstrap │ │ │ ├── bootstrap-slider.js │ │ │ ├── derived.js │ │ │ ├── excanvas.min.js │ │ │ ├── external-links.js │ │ │ ├── feedback.js │ │ │ ├── jquery-icontains.js │ │ │ ├── jquery-sortable.js │ │ │ ├── jquery.colorhelpers.min.js │ │ │ ├── jquery.flot.canvas.min.js │ │ │ ├── jquery.flot.categories.min.js │ │ │ ├── jquery.flot.crosshair.min.js │ │ │ ├── jquery.flot.errorbars.min.js │ │ │ ├── jquery.flot.fillbetween.min.js │ │ │ ├── jquery.flot.image.min.js │ │ │ ├── jquery.flot.min.js │ │ │ ├── jquery.flot.navigate.min.js │ │ │ ├── jquery.flot.pie.min.js │ │ │ ├── jquery.flot.ranges.min.js │ │ │ ├── jquery.flot.resize.min.js │ │ │ ├── jquery.flot.selection.min.js │ │ │ ├── jquery.flot.stack.min.js │ │ │ ├── jquery.flot.symbol.min.js │ │ │ ├── jquery.flot.threshold.min.js │ │ │ ├── jquery.flot.time.min.js │ │ │ ├── jquery.min.js │ │ │ ├── jquery.tablesorter │ │ │ ├── project │ │ │ │ ├── compare │ │ │ │ │ ├── colourize.js │ │ │ │ │ ├── draggable-headers.js │ │ │ │ │ ├── expandable-metrics.js │ │ │ │ │ └── scorebars.js │ │ │ │ ├── detail │ │ │ │ │ ├── graphs.js │ │ │ │ │ ├── layout.js │ │ │ │ │ └── metrics.js │ │ │ │ ├── health-score-utils.js │ │ │ │ └── index │ │ │ │ │ ├── compare.js │ │ │ │ │ ├── health-scores.js │ │ │ │ │ └── tablesorter.js │ │ │ └── ratelimit.js │ │ ├── pin-down.png │ │ ├── pin-up.png │ │ ├── print.png │ │ ├── redhat-logo.png │ │ ├── spinner.gif │ │ ├── trending-graph-down.png │ │ ├── trending-graph-equals.png │ │ ├── trending-graph-src.svg │ │ ├── trending-graph-up.png │ │ └── warning-na-metric.png │ ├── templates │ │ └── hmeter_frontend │ │ │ ├── about.html │ │ │ ├── all-projects.html │ │ │ ├── base.html │ │ │ ├── business-units.html │ │ │ ├── contact.html │ │ │ ├── migration-schedule.html │ │ │ └── project │ │ │ ├── compare.html │ │ │ ├── detail.html │ │ │ ├── emaildomains │ │ │ ├── fragments │ │ │ ├── detail │ │ │ │ ├── metric-tree.html │ │ │ │ └── sections │ │ │ │ │ ├── base.html │ │ │ │ │ ├── blog-graph.js │ │ │ │ │ ├── blogs-activity.html │ │ │ │ │ ├── bug-graphs.js │ │ │ │ │ ├── bug-report-activity.html │ │ │ │ │ ├── commit-activity.html │ │ │ │ │ ├── commit-graphs.js │ │ │ │ │ ├── general-quality-graphs.js │ │ │ │ │ ├── general-quality.html │ │ │ │ │ ├── mailing-list-activity.html │ │ │ │ │ ├── mailing-list-graphs.js │ │ │ │ │ ├── publicity.html │ │ │ │ │ ├── security.html │ │ │ │ │ └── tabbed.html │ │ │ └── project-listing-table.html │ │ │ ├── graph-data.js │ │ │ ├── index.html │ │ │ ├── metric-history.html │ │ │ └── problems.html │ ├── templatetags │ │ ├── __init__.py │ │ ├── divby.py │ │ ├── includeor.py │ │ ├── jsonify.py │ │ ├── metrics.py │ │ └── recurse.py │ ├── urls.py │ ├── utils │ │ ├── __init__.py │ │ ├── cache.py │ │ ├── compression.py │ │ ├── containers.py │ │ ├── filetools.py │ │ ├── iterators.py │ │ ├── mail.py │ │ ├── misc.py │ │ ├── path.py │ │ ├── stats.py │ │ └── web.py │ └── views │ │ ├── __init__.py │ │ ├── all_projects │ │ ├── __init__.py │ │ ├── first-three.json │ │ └── jboss-projects.json │ │ ├── business_units.py │ │ ├── feedback.py │ │ ├── project │ │ ├── __init__.py │ │ ├── compare.py │ │ ├── detail.py │ │ ├── graphs.py │ │ ├── index.py │ │ ├── metrichistory.py │ │ └── problems.py │ │ └── static.py ├── importerutils │ ├── __init__.py │ ├── exceptions.py │ ├── importers.py │ ├── management │ │ ├── __init__.py │ │ └── base.py │ ├── mptasks.py │ ├── registry.py │ ├── shortcuts.py │ └── signals.py ├── managers.py ├── mlinfo │ ├── __init__.py │ ├── admin.py │ ├── importers │ │ ├── __init__.py │ │ ├── common.py │ │ ├── http.py │ │ ├── importer.py │ │ ├── mbox.py │ │ └── nntp.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── update_mailing_lists.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── participantinfo │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── projectinfo │ ├── __init__.py │ ├── admin.py │ ├── decorators.py │ ├── fixtures │ │ ├── initial_business_units.json │ │ └── initial_products.json │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── dump_last_updated.py │ │ │ ├── dump_projects.py │ │ │ ├── import_releases.py │ │ │ └── list_projects.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── resources.py │ ├── tests.py │ └── views.py ├── settings │ ├── .gitignore │ ├── __init__.py │ ├── cardinal.py │ ├── cardinal1x.py │ ├── common.py │ ├── dev.py │ ├── djdt.py │ ├── openshift.py │ └── pseudo_openshift.py ├── templates │ └── 404.html ├── test-metrics.py ├── test-nntp-importer.py ├── urls.py ├── utils.py ├── vcsinfo │ ├── __init__.py │ ├── fixtures │ │ └── initial_data.json │ ├── importers │ │ ├── __init__.py │ │ ├── common.py │ │ ├── dvcsimporter.py │ │ └── git_importer.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── update_vcs_repositories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── views │ ├── __init__.py │ ├── errors.py │ └── generic.py ├── manage.py └── static └── README /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.sw[po] 3 | *.bak 4 | *.orig 5 | *~ 6 | .project 7 | .pydevproject 8 | HealthMeter.egg-info 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/bootstrap"] 2 | path = libs/bootstrap 3 | url = https://github.com/twbs/bootstrap 4 | [submodule "libs/bootstrap-slider"] 5 | path = libs/bootstrap-slider 6 | url = https://github.com/seiyria/bootstrap-slider 7 | [submodule "libs/jquery-sortable"] 8 | path = libs/jquery-sortable 9 | url = https://github.com/johnny/jquery-sortable 10 | [submodule "libs/jquery-treeview"] 11 | path = libs/jquery-treeview 12 | url = https://github.com/jzaefferer/jquery-treeview.git 13 | [submodule "libs/tablesorter"] 14 | path = libs/tablesorter 15 | url = https://github.com/Mottie/tablesorter 16 | [submodule "libs/django-activelink"] 17 | path = libs/django-activelink 18 | url = https://github.com/mauricioabreu/django-activelink 19 | branch = add-django-19-support 20 | -------------------------------------------------------------------------------- /.openshift/action_hooks/build: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | #!/bin/bash 5 | # Script cannibalized from https://github.com/zfdang/memcached-in-openshift to 6 | # install memcached into $OPENSHIFT_DATA_DIR 7 | 8 | optdir="$OPENSHIFT_DATA_DIR/opt" 9 | 10 | install_pkg() { 11 | local pkg="$1" \ 12 | version="$2" \ 13 | url_template="$3" \ 14 | from_source="$4" 15 | local builddir="$OPENSHIFT_TMP_DIR/$pkg-$version" \ 16 | prefix="$optdir/$pkg" 17 | 18 | shift 4 # everything else is to be passed to configure 19 | 20 | if [ -d "$prefix" ]; then 21 | echo "$prefix exists. Skipping compilation..." 22 | return 0 23 | fi 24 | 25 | ( 26 | set -e # Die on error 27 | 28 | echo "Cleaning up $builddir if it exists" 29 | rm -rf "$builddir" 30 | 31 | echo "Creating $builddir" 32 | mkdir "$builddir" 33 | 34 | eval "local url=$url_template" 35 | local tarball="$pkg-$version.tar.gz" 36 | 37 | echo "Downloading $pkg from $url to $OPENSHIFT_TMP_DIR/$tarball" 38 | 39 | cd "$OPENSHIFT_TMP_DIR" 40 | curl -L -o "$tarball" "$url" 41 | 42 | echo "Extracting $tarball" 43 | tar -C "$builddir" --strip-components=1 -xzvf "$pkg-$version.tar.gz" 44 | 45 | if [ "$from_source" = y ]; then 46 | echo "Compiling" 47 | cd "$builddir" 48 | 49 | sh configure --prefix="$prefix" "$@" 50 | make -j$(grep -c ^processor /proc/cpuinfo) 51 | 52 | echo "Installing into $prefix" 53 | make install 54 | 55 | else 56 | echo "Not from_source, so directly moving into $prefix" 57 | mv -T "$builddir" "$prefix" 58 | fi 59 | ) 60 | 61 | ret=$? 62 | if [ $ret -eq 0 ]; then 63 | echo "Successfully installed $pkg-$version" 64 | else 65 | echo "Installation of $pkg-$version failed" 66 | fi 67 | 68 | return $ret 69 | } 70 | 71 | mkdir -p $optdir 72 | 73 | install_pkg \ 74 | libevent \ 75 | 2.0.22 \ 76 | 'https://sourceforge.net/projects/levent/files/libevent/$pkg-2.0/libevent-$version-stable.tar.gz' \ 77 | y 78 | 79 | install_pkg \ 80 | memcached \ 81 | 1.4.24 \ 82 | 'http://www.memcached.org/files/$pkg-$version.tar.gz' \ 83 | y \ 84 | --with-libevent=$optdir/libevent 85 | 86 | install_pkg \ 87 | iojs \ 88 | 3.0.0 \ 89 | 'https://iojs.org/dist/v$version/$pkg-v$version-linux-x64.tar.gz' \ 90 | n 91 | 92 | lessdir="$optdir/less" 93 | mkdir -p "$lessdir" 94 | cd "$lessdir" 95 | HOME="$lessdir" $optdir/iojs/bin/npm install less 96 | -------------------------------------------------------------------------------- /.openshift/action_hooks/deploy: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | #!/bin/bash 5 | # This deploy hook gets executed after dependencies are resolved and the 6 | # build hook has been run but before the application has been started back 7 | # up again. This script gets executed directly, so it could be python, php, 8 | # ruby, etc. 9 | 10 | sleep 5 11 | # export PYTHON_EGG_CACHE=$OPENSHIFT_GEAR_DIR/virtenv/lib/python-2.6 12 | source $OPENSHIFT_PYTHON_DIR/virtenv/bin/activate 13 | 14 | runcmd() { 15 | echo "Executing '$*'" 16 | "$@" 17 | } 18 | 19 | managepy="$OPENSHIFT_REPO_DIR/wsgi/manage.py" 20 | 21 | manage() { 22 | runcmd python "$managepy" "$@" 23 | } 24 | 25 | manage syncdb --noinput 26 | manage migrate 27 | # manage collectstatic --noinput 28 | # runcmd mkdir -p $OPENSHIFT_DATA_DIR/staticgen 29 | # runcmd ln -fsT $OPENSHIFT_DATA_DIR/staticgen \ 30 | # $OPENSHIFT_REPO_DIR/wsgi/static/generated 31 | -------------------------------------------------------------------------------- /.openshift/action_hooks/post_deploy: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | #!/bin/bash 5 | 6 | set -e 7 | 8 | MEMCACHED_BINARY=${OPENSHIFT_DATA_DIR}opt/memcached/bin/memcached 9 | 10 | if [ ! -e MEMCACHED_BINARY ]; then 11 | pkill -U$(whoami) memcached || true 12 | $MEMCACHED_BINARY -l $OPENSHIFT_PYTHON_IP -p 22322 -m 64 -d 13 | echo "${MEMCACHED_BINARY} started!" 14 | 15 | else 16 | echo "============================================" 17 | echo "${MEMCACHED_BINARY} does not exist!." 18 | echo "You should manually install it ..." 19 | echo "============================================" 20 | fi 21 | -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_build: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | #!/bin/bash 5 | # This is a simple script and will be executed on your CI system if 6 | # available. Otherwise it will execute while your application is stopped 7 | # before the build step. This script gets executed directly, so it 8 | # could be python, php, ruby, etc. 9 | -------------------------------------------------------------------------------- /.openshift/cron/daily/warm_detailpage_cache: -------------------------------------------------------------------------------- 1 | ../../../scripts/warm_detailpage_cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE:** *Prospector project is inactive. The project will become active once someone starts maintaining it again.* 2 | 3 | Prospector 4 | 5 | Prospector permits automated collection of a wide range of metrics of 6 | open source projects useful in evaluating the project. 7 | 8 | Prospector was initiated by the Red Hat legal department in 2012 with 9 | the recognition that large portions of the software Industry are 10 | increasingly being driven by open source innovation. It was designed 11 | to provide a detailed, objective, and systematic approach to evaluating 12 | communities. This allows for assessment of the health and trends of 13 | open source projects. 14 | 15 | In 2017 Prospector was licensed under GPL v. 3 and became part of 16 | the CHAOSS Project. 17 | 18 | Initial development of Prospector at Red Hat was led by Harish Pillay 19 | hpillay@redhat.com and with a full time developer, Chow Loong Jin 20 | lchow@redhat.com 21 | 22 | 1 July 2017 23 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/data/.gitkeep -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/libs/.gitkeep -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=1.11 2 | psycopg2 3 | pillow 4 | beautifulsoup4 5 | python-dateutil 6 | python-graph-core 7 | python-magic 8 | python-memcached 9 | jsonfield 10 | logutils 11 | django-compressor 12 | django-bootstrap-form 13 | django-sekizai 14 | django-mptt 15 | django-medusa 16 | django-js-reverse 17 | django-colorful 18 | twython 19 | perceval>=0.9 20 | -------------------------------------------------------------------------------- /scripts/inve: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . $OPENSHIFT_PYTHON_DIR/virtenv/bin/activate 4 | 5 | exec "$@" 6 | -------------------------------------------------------------------------------- /scripts/run_task: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2017 Red Hat, Inc. 4 | # License: GPLv3 or any later version 5 | 6 | import os 7 | import sys 8 | 9 | os.environ['DJANGO_SETTINGS_MODULE'] = 'healthmeter.settings' 10 | 11 | if 'OPENSHIFT_REPO_DIR' in os.environ: 12 | repo_root = os.environ['OPENSHIFT_REPO_DIR'] 13 | virtenv = os.environ['OPENSHIFT_PYTHON_DIR'] + '/virtenv/' 14 | os.environ['PYTHON_EGG_CACHE'] = os.path.join( 15 | virtenv, 'lib/python2.6/site-packages') 16 | virtualenv = os.path.join(virtenv, 'bin/activate_this.py') 17 | 18 | try: 19 | execfile(virtualenv, dict(__file__=virtualenv)) 20 | except: 21 | pass 22 | 23 | else: 24 | this_dir = os.path.dirname(os.path.realpath(os.path.abspath(__file__))) 25 | repo_root = os.path.join(this_dir, '..') 26 | 27 | sys.path.extend([os.path.join(repo_root, 'wsgi', 'healthmeter'), 28 | os.path.join(repo_root, 'wsgi'), 29 | os.path.join(repo_root, 'libs')]) 30 | 31 | task_name = os.path.basename(__file__) 32 | 33 | from django.core.management import call_command 34 | call_command(task_name) 35 | -------------------------------------------------------------------------------- /scripts/update_blogs: -------------------------------------------------------------------------------- 1 | ./run_task -------------------------------------------------------------------------------- /scripts/update_bug_trackers: -------------------------------------------------------------------------------- 1 | run_task -------------------------------------------------------------------------------- /scripts/update_irc_meetings: -------------------------------------------------------------------------------- 1 | run_task -------------------------------------------------------------------------------- /scripts/update_mailing_lists: -------------------------------------------------------------------------------- 1 | ./run_task -------------------------------------------------------------------------------- /scripts/update_vcs_repositories: -------------------------------------------------------------------------------- 1 | ./run_task -------------------------------------------------------------------------------- /scripts/warm_detailpage_cache: -------------------------------------------------------------------------------- 1 | run_task -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2017 Red Hat, Inc. 3 | # License: GPLv3 or any later version 4 | 5 | import os 6 | from setuptools import setup 7 | 8 | install_requires = [ 9 | 'Django>=1.11', 10 | 'psycopg2', 11 | 'pillow', 12 | 'beautifulsoup4', 13 | 'python-dateutil', 14 | 'python-graph-core', 15 | 'python-magic', 16 | 'python-memcached', 17 | 'jsonfield', 18 | 'logutils', # required for django-medusa 19 | 'django-compressor', 20 | 'django-bootstrap-form', 21 | 'django-sekizai', 22 | 'django-mptt', 23 | 'django-medusa', 24 | 'django-js-reverse', 25 | 'twython', 26 | 'perceval' 27 | ] 28 | 29 | # Hack for installing Django package before 30 | # some dependent packages 31 | setup_requires = [ 32 | 'django-colorful', 33 | ] 34 | 35 | if not os.getenv('OPENSHIFT_APP_NAME'): 36 | install_requires.append('lxml') 37 | 38 | # Required for django-medusa 39 | try: 40 | import importlib 41 | except ImportError: 42 | install_requires.append('importlib') 43 | 44 | setup( 45 | name='HealthMeter', 46 | version='1.0', 47 | description='Project for assessing the health of open source projects', 48 | author='Chow Loong Jin', 49 | author_email='lchow@redhat.com', 50 | url='http://www.python.org/sigs/distutils-sig/', 51 | install_requires=install_requires 52 | # dependency_links=[ 53 | # 'git+git://github.com/hyperair/django-preferences.git' 54 | # '#egg=django-preferences-0.0.6.1', 55 | # ] 56 | ) 57 | -------------------------------------------------------------------------------- /wsgi/.gitignore: -------------------------------------------------------------------------------- 1 | /media/ 2 | -------------------------------------------------------------------------------- /wsgi/.htaccess: -------------------------------------------------------------------------------- 1 | DirectoryIndex index.html index.json 2 | RewriteEngine On 3 | 4 | RewriteCond %{REQUEST_URI} !.*project/\+compare/.* 5 | RewriteCond %{REQUEST_URI} !.*admin/.* 6 | RewriteCond %{REQUEST_URI} !.*jsreverse.* 7 | RewriteCond %{REQUEST_URI} !.*feedback 8 | RewriteCond %{REQUEST_URI} !.*media.* 9 | RewriteRule ^application/(.+)$ static/generated/$1 10 | -------------------------------------------------------------------------------- /wsgi/application: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2017 Red Hat, Inc. 4 | # License: GPLv3 or any later version 5 | 6 | import os 7 | import sys 8 | import logging 9 | 10 | os.environ['DJANGO_SETTINGS_MODULE'] = 'healthmeter.settings' 11 | 12 | sys.path.append(os.path.dirname(__file__)) 13 | sys.path.append(os.path.join(os.path.dirname(__file__), 'healthmeter')) 14 | 15 | application_ = None 16 | 17 | logging.basicConfig(stream=sys.stderr, 18 | level=logging.INFO) 19 | logger = logging.getLogger(__name__) 20 | 21 | 22 | def prepare(): 23 | virtenv = os.environ['APPDIR'] + '/virtenv/' 24 | os.environ['PYTHON_EGG_CACHE'] = os.path.join( 25 | virtenv, 'lib/python2.6/site-packages') 26 | 27 | try: 28 | virtualenv = os.path.join(virtenv, 'bin/activate_this.py') 29 | execfile(virtualenv, dict(__file__=virtualenv)) 30 | except: 31 | logger.warn("Could not activate virtualenv %s", virtualenv, 32 | exc_info=True) 33 | 34 | 35 | def get_application(): 36 | global application_ 37 | if application_: 38 | return application_ 39 | 40 | prepare() 41 | from django.core.handlers.wsgi import WSGIHandler 42 | application_ = WSGIHandler() 43 | 44 | return application_ 45 | 46 | 47 | def application(environ, start_response): 48 | env_whitelist = set(('APPDIR', 'BASEURL')) 49 | for k, v in environ.items(): 50 | if k.startswith('OPENSHIFT_') or k in env_whitelist: 51 | os.environ[k] = v 52 | 53 | return get_application()(environ, start_response) 54 | -------------------------------------------------------------------------------- /wsgi/healthmeter/.gitignore: -------------------------------------------------------------------------------- 1 | sqlite3.db 2 | -------------------------------------------------------------------------------- /wsgi/healthmeter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/bloginfo/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/importers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .common import BlogImporter 5 | from . import rss 6 | -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/importers/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from healthmeter.importerutils.importers import ImporterBase 5 | from healthmeter.bloginfo.models import Blog 6 | 7 | 8 | class BlogImporter(ImporterBase): 9 | """Generic blog post importer class""" 10 | model = Blog 11 | 12 | @classmethod 13 | def resolve_importer_type(cls, blog): 14 | return 'rss' 15 | -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/bloginfo/management/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/bloginfo/management/commands/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/management/commands/update_blogs.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from healthmeter.bloginfo.importers import BlogImporter 5 | from healthmeter.bloginfo.models import Blog 6 | 7 | from healthmeter.importerutils.management.base import ImporterCommand 8 | 9 | 10 | class Command(ImporterCommand): 11 | importer = BlogImporter 12 | model = Blog 13 | -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-05 03:35 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import mptt.fields 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('projectinfo', '0001_initial'), 16 | ('participantinfo', '__first__'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Blog', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('rss_url', models.URLField(max_length=255, unique=True)), 25 | ('url', models.URLField(default='', max_length=255)), 26 | ('last_updated', models.DateTimeField(blank=True, default=None, null=True)), 27 | ('projects', mptt.fields.TreeManyToManyField(blank=True, related_name='blogs', to='projectinfo.Project')), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='Post', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('timestamp', models.DateTimeField()), 35 | ('guid', models.CharField(default='', max_length=255)), 36 | ('title', models.CharField(default='', max_length=255)), 37 | ('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='participantinfo.Participant')), 38 | ('blog', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='posts', to='bloginfo.Blog')), 39 | ], 40 | ), 41 | migrations.AlterUniqueTogether( 42 | name='post', 43 | unique_together=set([('blog', 'guid')]), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/bloginfo/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/bloginfo/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.db import models 5 | from healthmeter.managers import get_natural_key_manager 6 | from healthmeter.participantinfo.models import Participant 7 | from healthmeter.projectinfo.models import Project 8 | from healthmeter.projectinfo.decorators import resource 9 | from mptt.models import TreeForeignKey, TreeManyToManyField 10 | 11 | 12 | @resource 13 | class Blog(models.Model): 14 | rss_url = models.URLField(max_length=255, unique=True) 15 | url = models.URLField(max_length=255, default='') 16 | projects = TreeManyToManyField(Project, related_name='blogs', blank=True) 17 | 18 | last_updated = models.DateTimeField(blank=True, null=True, default=None) 19 | 20 | objects = get_natural_key_manager('rss_url') 21 | 22 | def __str__(self): 23 | return self.url 24 | 25 | 26 | class Post(models.Model): 27 | blog = models.ForeignKey(Blog, related_name='posts') 28 | timestamp = models.DateTimeField() 29 | author = models.ForeignKey(Participant, null=True) 30 | guid = models.CharField(max_length=255, default='') 31 | 32 | title = models.CharField(max_length=255, default='') 33 | 34 | class Meta: 35 | unique_together = ('blog', 'guid') 36 | 37 | def __str__(self): 38 | return '%s on %s' % (self.guid, self.blog) 39 | -------------------------------------------------------------------------------- /wsgi/healthmeter/bootstrap.sql: -------------------------------------------------------------------------------- 1 | # MySQL script to create the user and database needed to deploy the application, 2 | # adapted from hackdo (github.com/HackerspaceSG/hackdo) 3 | # 4 | # Copyright 2011, Ruiwen Chua et. al 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | 24 | CREATE USER 'healthmeter'@'localhost' IDENTIFIED BY 'healthmeter'; 25 | CREATE DATABASE IF NOT EXISTS healthmeter COLLATE utf8_general_ci; 26 | GRANT ALL ON healthmeter.* TO 'healthmeter'@'localhost'; 27 | GRANT ALL PRIVILEGES ON `healthmeter\_%` . * TO 'healthmeter'@'localhost'; 28 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/btinfo/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.contrib import admin 5 | from .models import * 6 | 7 | admin.site.register(BugTracker) 8 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "btinfo.type", 5 | "fields": { 6 | "name": "bugzilla" 7 | } 8 | }, 9 | { 10 | "pk": 2, 11 | "model": "btinfo.type", 12 | "fields": { 13 | "name": "jira" 14 | } 15 | }, 16 | { 17 | "pk": 3, 18 | "model": "btinfo.type", 19 | "fields": { 20 | "name": "github" 21 | } 22 | }, 23 | { 24 | "pk": 5, 25 | "model": "btinfo.type", 26 | "fields": { 27 | "name": "redmine" 28 | } 29 | }, 30 | { 31 | "pk": 1, 32 | "model": "btinfo.severity", 33 | "fields": { 34 | "name": "Low", 35 | "level": 1 36 | } 37 | }, 38 | { 39 | "pk": 2, 40 | "model": "btinfo.severity", 41 | "fields": { 42 | "name": "Medium", 43 | "level": 2 44 | } 45 | }, 46 | { 47 | "pk": 3, 48 | "model": "btinfo.severity", 49 | "fields": { 50 | "name": "High", 51 | "level": 3 52 | } 53 | }, 54 | { 55 | "pk": 4, 56 | "model": "btinfo.severity", 57 | "fields": { 58 | "name": "Urgent", 59 | "level": 4 60 | } 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/importers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .common import BugTrackerImporter 5 | from . import (bugzilla_importer, jira_importer, github_importer, 6 | redmine_importer) 7 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/importers/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import logging 5 | from healthmeter.importerutils.importers import ImporterBase 6 | 7 | from healthmeter.btinfo.models import BugNamespace, Severity 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class BugTrackerImporter(ImporterBase): 13 | """Generic bug tracker information importer class""" 14 | severity_level_map = {} 15 | model = BugNamespace 16 | 17 | @classmethod 18 | def resolve_importer_type(cls, bt_info): 19 | return bt_info.bug_tracker.bt_type.name 20 | 21 | def __init__(self, bt_info): 22 | super().__init__(bt_info) 23 | self.severities = {} 24 | 25 | for severity in Severity.objects.all(): 26 | self.severities[severity.level] = severity 27 | 28 | def translate_severity(self, severity): 29 | """ 30 | Convert severity string retrieved from remote bug tracker into a 31 | Severity object 32 | 33 | @arg severity String containing severity. Will be looked up in 34 | self.severity_level_map to retrieve numeric level. 35 | @return btinfo.models.Severity object for this severity 36 | """ 37 | try: 38 | return self.severities[self.severity_level_map[severity]] 39 | 40 | except KeyError: 41 | logger.warning("Unknown severity [%s]", severity) 42 | return None 43 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/btinfo/management/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/btinfo/management/commands/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/management/commands/setup_github.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from __future__ import print_function 5 | import json 6 | import requests 7 | 8 | from django.core.management.base import BaseCommand, CommandError 9 | from healthmeter.btinfo.models import BugTracker 10 | 11 | 12 | class Command(BaseCommand): 13 | args = '' 14 | help = """ 15 | Runs through the Github setup process for the bug tracker ID specified on the 16 | command line. 17 | """ 18 | 19 | def handle(self, btid, **kwargs): 20 | obj = BugTracker.objects.get(id=btid) 21 | 22 | if obj.username or obj.password: 23 | raise CommandError("Username or password already specified on " 24 | "{0}".format(obj)) 25 | 26 | print("Please insert your Github client ID and secret.\n" 27 | "You may obtain a pair at " 28 | "https://github.com/settings/applications/new") 29 | 30 | client_id = raw_input("Client ID: ") 31 | client_secret = raw_input("Client secret: ") 32 | 33 | username = raw_input("Github username: ") 34 | password = raw_input("Github password: ") 35 | 36 | print("Requesting OAUTH token...") 37 | response = requests.post('https://api.github.com/authorizations', 38 | auth=(username, password), 39 | data=json.dumps( 40 | {'client_id': client_id, 41 | 'client_secret': client_secret, 42 | 'note': 'Health meter issues importer'})) 43 | if response.status_code != 201: 44 | raise CommandError("Could not get OAUTH token", 45 | response.headers, response.content) 46 | 47 | token = response.json()['token'] 48 | print("Got token: {0}".format(token)) 49 | 50 | # Github authorization uses basic http auth with 51 | # username:password = oauth:x-oauth-basic 52 | obj.username = token 53 | obj.password = 'x-oauth-basic' 54 | obj.save() 55 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/management/commands/update_bug_trackers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from healthmeter.importerutils.management.base import ImporterCommand 5 | from healthmeter.btinfo.importers import BugTrackerImporter 6 | from healthmeter.btinfo.models import BugNamespace 7 | 8 | 9 | class Command(ImporterCommand): 10 | importer = BugTrackerImporter 11 | model = BugNamespace 12 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/migrations/0002_bugtracker_api_token.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-05 22:49 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import healthmeter.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('btinfo', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='bugtracker', 18 | name='api_token', 19 | field=healthmeter.fields.PlaintextPasswordField(blank=True, max_length=255, null=True), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/btinfo/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/btinfo/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | # Create your views here. 5 | -------------------------------------------------------------------------------- /wsgi/healthmeter/eventinfo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/eventinfo/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/eventinfo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.5 on 2017-09-08 07:19 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import mptt.fields 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('projectinfo', '0001_initial'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Event', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('desc', models.CharField(max_length=255)), 24 | ('date_start', models.DateField()), 25 | ('date_end', models.DateField()), 26 | ('project', mptt.fields.TreeForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projectinfo.Project')), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /wsgi/healthmeter/eventinfo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/eventinfo/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/eventinfo/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from mptt.models import TreeForeignKey 3 | from healthmeter.projectinfo.models import Project 4 | 5 | 6 | class Event(models.Model): 7 | project = TreeForeignKey(Project) 8 | desc = models.CharField(max_length=255) 9 | date_start = models.DateField() 10 | date_end = models.DateField() 11 | 12 | def clean(self): 13 | """Check that date_end is after date_start""" 14 | if self.date_start > self.date_end: 15 | raise ValidationError("Ending date must come after starting date " 16 | "of event") 17 | 18 | def __str__(self): 19 | return '%s (%s-%s)' % (self.desc, self.date_start, self.date_end) 20 | -------------------------------------------------------------------------------- /wsgi/healthmeter/fields.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django import forms 5 | from django.db import models 6 | 7 | 8 | class PlaintextPasswordField(models.CharField): 9 | def formfield(self, form_class=forms.CharField, **kwargs): 10 | kwargs['widget'] = forms.PasswordInput 11 | 12 | return super().formfield(form_class=form_class, **kwargs) 13 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.contrib import admin 5 | from mptt.admin import MPTTModelAdmin 6 | 7 | from .models import * 8 | from healthmeter.btinfo.models import BugNamespace 9 | from healthmeter.bloginfo.models import Blog 10 | from healthmeter.eventinfo.models import Event 11 | from healthmeter.mlinfo.models import MailingList, MailingListProject 12 | from healthmeter.projectinfo.models import Release, License 13 | from healthmeter.vcsinfo.models import Repository 14 | 15 | for model in (Blog, Event, 16 | MetricScoreConstants, 17 | Repository, MailingList, MailingListProject, 18 | BugNamespace): 19 | admin.site.register(model) 20 | 21 | 22 | def make_inline(model_, extra_=0): 23 | class Inline(admin.StackedInline): 24 | model = model_ 25 | extra = extra_ 26 | 27 | return Inline 28 | 29 | 30 | class MetricAdmin(MPTTModelAdmin): 31 | mptt_level_indent = 20 32 | inlines = [make_inline(Metric)] 33 | list_display = ('title', 'weight', 'algorithm') 34 | 35 | admin.site.register(Metric, MetricAdmin) 36 | 37 | 38 | class BugNamespaceInline(admin.StackedInline): 39 | verbose_name = "Bug Namespace-Project Relationships" 40 | verbose_name_plural = "Bug Namespace-Project Relationships" 41 | model = BugNamespace.projects.through 42 | extra = 0 43 | 44 | 45 | class ProjectInline(admin.StackedInline): 46 | verbose_name = "Subproject" 47 | verbose_name_plural = "Subprojects" 48 | model = Project 49 | extra = 0 50 | 51 | 52 | class ProjectAdmin(MPTTModelAdmin): 53 | inlines = [ProjectInline, 54 | make_inline(Release), 55 | make_inline(Repository), 56 | BugNamespaceInline, 57 | make_inline(Event), 58 | make_inline(MailingListProject)] 59 | mptt_level_indent = 20 60 | filter_horizontal = ['licenses'] 61 | list_display = ('name', 'business_unit', 'is_wip') 62 | list_filter = ('business_unit', 'is_wip') 63 | 64 | admin.site.register(Project, ProjectAdmin) 65 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/context_processors.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .forms import FeedbackForm 5 | 6 | 7 | def feedback_form(context): 8 | if 'feedback_form' in context: 9 | return {} 10 | 11 | return { 12 | 'feedback_form': FeedbackForm() 13 | } 14 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/forms.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django import forms 5 | 6 | 7 | class FeedbackForm(forms.Form): 8 | name = forms.CharField() 9 | email = forms.EmailField() 10 | subject = forms.CharField() 11 | message = forms.CharField(widget=forms.Textarea) 12 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/handlers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.db.models import Q 5 | from django.dispatch import receiver 6 | import logging 7 | 8 | from healthmeter.importerutils.signals import import_finished 9 | from healthmeter.hmeter_frontend.models import Project, MetricCache 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | @receiver(import_finished) 15 | def invalidate_metrics(start, end, importer, **kwargs): 16 | projects = importer.object.get_projects() 17 | all_projects = Project.objects.filter(id__in=[p.id for p in projects]) \ 18 | .get_ancestors() 19 | 20 | range_query = Q() 21 | 22 | if start is not None: 23 | if end is not None: 24 | range_query |= (Q(start__lte=start, end__gte=start) | 25 | Q(start__lte=end, end__gte=end) | 26 | Q(start__gte=start, end__lte=end) | 27 | Q(start__isnull=True, end__gte=start) | 28 | Q(end__isnull=True, start__lte=end)) 29 | 30 | else: 31 | range_query |= Q(end__gte=start) 32 | 33 | else: 34 | if end is not None: 35 | range_query |= Q(start__lte=end) 36 | 37 | scores = MetricCache.objects.filter(Q(project__in=all_projects) & 38 | range_query) 39 | 40 | logger.info("Invalidating [%s] scores in range [%s, %s] for %s", 41 | scores.count(), start, end, list(all_projects)) 42 | 43 | scores.update(is_dirty=True) 44 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/hmeter_frontend/management/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/hmeter_frontend/management/commands/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/management/commands/clear_cache.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from django.core.cache import cache 6 | 7 | 8 | class Command(BaseCommand): 9 | args = '...' 10 | help = 'Clear cache keys by setting them to None' 11 | 12 | def handle(self, *args, **options): 13 | cache.set_many(dict((key, None) for key in args)) 14 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/management/commands/render_paths.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.conf import settings 5 | from django.core.management.base import BaseCommand, CommandError 6 | from django.core.urlresolvers import set_script_prefix 7 | from healthmeter.hmeter_frontend.renderers import ManualRenderer 8 | 9 | 10 | class Command(BaseCommand): 11 | args = ' [...]' 12 | help = 'Renderes on path using medusa' 13 | 14 | def handle(self, *paths, **_): 15 | if not paths: 16 | raise CommandError("Need to supply at least one path to render.") 17 | 18 | url_prefix = getattr(settings, 'MEDUSA_URL_PREFIX', None) 19 | if url_prefix is not None: 20 | set_script_prefix(url_prefix) 21 | 22 | ManualRenderer.initialize_output() 23 | renderer = ManualRenderer(paths) 24 | renderer.generate() 25 | ManualRenderer.finalize_output() 26 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/management/commands/render_project.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from django.core.management import call_command 6 | from django.core.urlresolvers import normalize, reverse, NoReverseMatch 7 | from healthmeter.hmeter_frontend.renderers import ProjectRenderer 8 | import itertools 9 | 10 | 11 | class Command(BaseCommand): 12 | args = ' [...]' 13 | 14 | def handle(self, *projids, **_): 15 | if not projids: 16 | raise CommandError("Need to supply at least one project id") 17 | 18 | paths = reduce(lambda x, y: x | y, 19 | (ProjectRenderer.get_paths_for_project(projid) 20 | for projid in projids)) 21 | 22 | call_command('render_paths', *paths) 23 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/management/commands/warm_detailpage_cache.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | 6 | from healthmeter.hmeter_frontend.models import Project 7 | from healthmeter.hmeter_frontend.views.project.detail import ProjectDetail 8 | 9 | 10 | class Command(BaseCommand): 11 | args = '[...]' 12 | help = 'Updates the ProjectDetail view cache' 13 | 14 | def handle(self, *args, **options): 15 | projects = list(Project.objects.filter(id__in=args) if args 16 | else Project.objects.root_nodes()) 17 | 18 | for project in projects: 19 | view = ProjectDetail() 20 | view.object = project 21 | view.get_projdata(_djcached_force_recache=True) 22 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/hmeter_frontend/metrics/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from . import bugs 5 | from . import events 6 | from . import infrastructure 7 | from . import mailing_list 8 | from . import publicity 9 | from . import quality 10 | from . import release 11 | from . import vcs 12 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/algorithms/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import abc 5 | from functools import wraps 6 | from healthmeter.hmeter_frontend.utils import (cached_property, djcached, 7 | project_cache_key_template) 8 | from ..constants import red_score, yellow_score, green_score 9 | 10 | 11 | class MetricAlgorithm(object): 12 | """ 13 | Base class for implementing Metrics 14 | """ 15 | __metaclass__ = abc.ABCMeta 16 | 17 | def score_project(self, project, start=None, end=None): 18 | self.project = project 19 | self.start = start 20 | self.end = end 21 | 22 | # Pre-evaluate this so that it gets cached 23 | return self.normalized_score 24 | 25 | @property 26 | def projects(self): 27 | return self.project.all_projects 28 | 29 | @property 30 | def green_score(self): 31 | return green_score() 32 | 33 | @property 34 | def yellow_score(self): 35 | return yellow_score() 36 | 37 | @property 38 | def red_score(self): 39 | return red_score() 40 | 41 | @cached_property 42 | def raw_value(self): 43 | return self.get_raw_value() 44 | 45 | @abc.abstractproperty 46 | def normalizer(): 47 | pass 48 | 49 | @cached_property 50 | def normalized_score(self): 51 | return self.normalizer(self.raw_value) 52 | 53 | @abc.abstractmethod 54 | def get_raw_value(self): 55 | pass 56 | 57 | @classmethod 58 | def as_metric(cls, *initargs, **initkwargs): 59 | @wraps(cls) 60 | def metricfn(project, start=None, end=None): 61 | metric_inst = cls(*initargs, **initkwargs) 62 | metric_inst.score_project(project, start, end) 63 | return metric_inst 64 | return metricfn 65 | 66 | @property 67 | @djcached( 68 | lambda self: project_cache_key_template.format( 69 | id=self.project.id, 70 | key=type(self).__name__ + ':is_complete'), 71 | 6 * 3600) 72 | def is_complete(self): 73 | return self._is_complete 74 | 75 | @abc.abstractproperty 76 | def _is_complete(self): 77 | pass 78 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/algorithms/events.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import datetime 5 | from healthmeter.hmeter_frontend.utils.iterators import intervals 6 | from healthmeter.eventinfo import models as eventmodels 7 | from ..lookup import metric 8 | 9 | from .mixins import FrequencyMetric, NullChecker, TimeBasedMetric 10 | from .normalizers import ClampingNormalizer 11 | 12 | 13 | class EventMetric(NullChecker, TimeBasedMetric): 14 | @property 15 | def events(self): 16 | qs = eventmodels.Event.objects.filter(project__in=self.projects) \ 17 | .order_by('date_start') 18 | return self.clamp_timestamp(qs, 'date_start') 19 | 20 | @property 21 | def event_intervals(self): 22 | timestamps = (x.date_start for x in self.events) 23 | return (x.days for x in intervals(timestamps)) 24 | 25 | 26 | @metric 27 | class EventFrequency(FrequencyMetric, EventMetric): 28 | normalizer = ClampingNormalizer(green_threshold=2, 29 | yellow_threshold=1) 30 | 31 | unit = 'event per month' 32 | unit_plural = 'events per month' 33 | 34 | def get_quantity(self): 35 | return self.events.count() 36 | 37 | def get_period(self): 38 | first_date = self.events[0].date_start 39 | last_date = self.end.date() if self.end else datetime.date.today() 40 | return (last_date - first_date).days / 30.0 41 | 42 | 43 | @metric 44 | class EventMaxInterval(EventMetric): 45 | normalizer = ClampingNormalizer(green_threshold=2, yellow_threshold=1) 46 | 47 | unit = 'day' 48 | 49 | def get_raw_value(self): 50 | try: 51 | return max(x / 30.0 for x in self.event_intervals) 52 | except ValueError: 53 | return None 54 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/algorithms/infrastructure.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .base import MetricAlgorithm 5 | from .normalizers import BoolNormalizer 6 | from .mixins import NullChecker 7 | 8 | from ..lookup import metric 9 | 10 | import healthmeter.projectinfo.models as projectmodels 11 | 12 | 13 | @metric 14 | class HasWebsite(NullChecker, MetricAlgorithm): 15 | normalizer = BoolNormalizer() 16 | 17 | def get_raw_value(self): 18 | return bool(self.project.website_url) 19 | 20 | 21 | @metric 22 | class HasMailingList(NullChecker, MetricAlgorithm): 23 | normalizer = BoolNormalizer() 24 | 25 | def get_raw_value(self): 26 | return self.project.all_mailing_lists.exists() 27 | 28 | 29 | @metric 30 | class HasDescription(NullChecker, MetricAlgorithm): 31 | normalizer = BoolNormalizer() 32 | 33 | def get_raw_value(self): 34 | return bool(self.project.description) 35 | 36 | 37 | @metric 38 | class CLAAbsent(NullChecker, MetricAlgorithm): 39 | normalizer = BoolNormalizer() 40 | 41 | def get_raw_value(self): 42 | return not self.project.has_contributor_agreement 43 | 44 | 45 | @metric 46 | class HasOSIApprovedLicense(NullChecker, MetricAlgorithm): 47 | normalizer = BoolNormalizer() 48 | 49 | @property 50 | def licenses(self): 51 | return projectmodels.License.objects.filter(projects__in=self.projects) 52 | 53 | def get_raw_value(self): 54 | return self.licenses.filter(is_osi_approved=True).exists() 55 | 56 | 57 | @metric 58 | class HasBugTracker(NullChecker, MetricAlgorithm): 59 | normalizer = BoolNormalizer() 60 | 61 | def get_raw_value(self): 62 | return self.project.all_bug_trackers.exists() 63 | 64 | 65 | @metric 66 | class HasGovernanceModel(NullChecker, MetricAlgorithm): 67 | normalizer = BoolNormalizer() 68 | 69 | def get_raw_value(self): 70 | return bool(self.project.governance) 71 | 72 | 73 | @metric 74 | class HasVcs(NullChecker, MetricAlgorithm): 75 | normalizer = BoolNormalizer() 76 | 77 | def get_raw_value(self): 78 | return self.project.all_vcs_repositories.exists() 79 | 80 | 81 | @metric 82 | class HasBlog(NullChecker, MetricAlgorithm): 83 | normalizer = BoolNormalizer() 84 | 85 | def get_raw_value(self): 86 | return self.project.all_blogs.exists() 87 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/algorithms/publicity.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import datetime 5 | from django.db.models import Min, Max 6 | from healthmeter.hmeter_frontend import models 7 | from healthmeter.bloginfo import models as blogmodels 8 | from healthmeter.hmeter_frontend.utils.iterators import intervals 9 | 10 | from ..lookup import metric 11 | from .mixins import (FrequencyMetric, LastUpdatedChecker, TimeBasedMetric, 12 | UnimplementedMetric) 13 | from .normalizers import ClampingNormalizer 14 | 15 | 16 | # Blog metrics 17 | class BlogMetric(LastUpdatedChecker, TimeBasedMetric): 18 | @property 19 | def posts(self): 20 | qs = blogmodels.Post.objects.filter(blog__projects__in=self.projects) \ 21 | .order_by('timestamp') 22 | return self.clamp_timestamp(qs, 'timestamp') 23 | 24 | @property 25 | def blogs(self): 26 | return self.project.all_blogs 27 | 28 | queryset = blogs 29 | 30 | @property 31 | def post_intervals(self): 32 | timestamps = (post.timestamp for post in self.posts) 33 | return (interval.days for interval in intervals(timestamps)) 34 | 35 | 36 | @metric 37 | class BlogPostFrequency(FrequencyMetric, BlogMetric): 38 | normalizer = ClampingNormalizer(green_threshold=5, yellow_threshold=2) 39 | 40 | unit = 'blog post per month' 41 | unit_plural = 'blog posts per month' 42 | 43 | def get_quantity(self): 44 | return self.posts.count() 45 | 46 | def get_period(self): 47 | dates = self.posts.aggregate(first=Min('timestamp'), 48 | last=Max('timestamp')) 49 | 50 | return ((dates['last'] - dates['first']).days / 51 | 30.0) 52 | 53 | 54 | @metric 55 | class BlogPostMinInterval(BlogMetric): 56 | normalizer = ClampingNormalizer(green_threshold=10, yellow_threshold=6) 57 | 58 | unit = 'day' 59 | 60 | def get_raw_value(self): 61 | try: 62 | return min(self.post_intervals) 63 | except ValueError: 64 | return None 65 | 66 | 67 | @metric 68 | class BlogPostMaxInterval(BlogMetric): 69 | normalizer = ClampingNormalizer(green_threshold=10, yellow_threshold=6) 70 | 71 | unit = 'day' 72 | 73 | def get_raw_value(self): 74 | try: 75 | return max(self.post_intervals) 76 | except ValueError: 77 | return None 78 | 79 | 80 | # Publicity metrics 81 | @metric 82 | class DownloadFrequency(UnimplementedMetric): 83 | pass 84 | 85 | 86 | @metric 87 | class DownloadFrequency(UnimplementedMetric): 88 | pass 89 | 90 | 91 | @metric 92 | class GoogleTrendsIsUpwards(UnimplementedMetric): 93 | pass 94 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/algorithms/quality.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.db.models import Sum 5 | import itertools 6 | from healthmeter.hmeter_frontend import models 7 | import healthmeter.vcsinfo.models as vcsmodels 8 | 9 | 10 | from ..lookup import metric 11 | from .mixins import RatioMetric 12 | from .normalizers import ClampingNormalizer 13 | from .vcs import VCSMetric 14 | from .bugs import BugReportMetric 15 | 16 | 17 | class SlocMetric(VCSMetric): 18 | @property 19 | def sloc(self): 20 | return 0 21 | 22 | 23 | @metric 24 | class BugsPerKLOC(RatioMetric, BugReportMetric, SlocMetric): 25 | normalizer = ClampingNormalizer(green_threshold=5.0, yellow_threshold=10.0) 26 | 27 | unit = 'bug per KLOC' 28 | unit_plural = 'bugs per KLOC' 29 | 30 | def get_denominator(self): 31 | sloc = self.sloc 32 | 33 | return sloc if sloc is not None else None 34 | 35 | def get_numerator(self): 36 | if not self.bugnamespaces.exists(): 37 | return None 38 | 39 | return self.first_comments.count() 40 | 41 | 42 | @metric 43 | class BugsClosedRatio(RatioMetric, BugReportMetric): 44 | normalizer = ClampingNormalizer(green_threshold=1.0, yellow_threshold=2.0) 45 | 46 | def get_denominator(self): 47 | return self.first_comments.count() 48 | 49 | def get_numerator(self): 50 | return self.first_comments.filter(bug__close_date__isnull=False) \ 51 | .count() 52 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/algorithms/release.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import healthmeter.projectinfo.models as projectmodels 5 | import datetime 6 | 7 | from ..lookup import metric 8 | from .base import MetricAlgorithm 9 | from .mixins import NullChecker, TimeBasedMetric 10 | from .normalizers import ClampingNormalizer 11 | 12 | 13 | @metric 14 | class TimeSinceLastSeenRelease(NullChecker, TimeBasedMetric, MetricAlgorithm): 15 | normalizer = ClampingNormalizer(green_threshold=6 * 30, 16 | yellow_threshold=12 * 30) 17 | 18 | unit = 'day' 19 | 20 | def get_raw_value(self): 21 | try: 22 | releases = self.clamp_timestamp(self.project.all_releases, 'date') 23 | latest = releases.latest('date').date 24 | 25 | except projectmodels.Release.DoesNotExist: 26 | return None 27 | 28 | end = (self.end.date() if self.end is not None 29 | else datetime.date.today()) 30 | 31 | return (end - latest).days 32 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/calc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import healthmeter.hmeter_frontend.metrics.algorithms 5 | from healthmeter.hmeter_frontend.models import MetricCache 6 | from .lookup import get_metricalgo 7 | 8 | 9 | def calc_score(project, metric, start=None, end=None): 10 | """ 11 | Applies the metric tree in `metric_tree` to `project` to derive a score 12 | tree. 13 | 14 | :param project: Project to apply metric tree to 15 | :param metric_tree: Root Metric object to compute scores for 16 | """ 17 | 18 | if metric.algorithm is None: # no algo, fall back on weighted average 19 | children = [calc_score(project, child, start, end) 20 | for child in metric.get_children()] 21 | 22 | numerator = sum((result.weight * result.score) 23 | for result in children) 24 | denominator = sum(result.weight for result in children) 25 | score = float(numerator) / denominator if denominator else None 26 | raw_value = None 27 | is_complete = all(child.is_complete for child in children) 28 | 29 | else: 30 | children = [] 31 | algo = get_metricalgo(metric.algorithm.name)(project, start, end) 32 | score = algo.normalized_score 33 | raw_value = algo.raw_value 34 | is_complete = algo.is_complete 35 | 36 | return MetricCache(project=project, 37 | metric=metric, 38 | raw_value=raw_value, 39 | score=score, 40 | start=start, 41 | end=end, 42 | is_complete=is_complete, 43 | children=children) 44 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | score_constants = None 5 | 6 | 7 | def get_score_constants(): 8 | global score_constants 9 | 10 | if score_constants is None: 11 | # Lazy-import this module to avoid circular deps 12 | from .. import models 13 | score_constants = models.MetricScoreConstants.objects.all()[:1].get() 14 | 15 | return score_constants 16 | 17 | 18 | def green_score(): 19 | return get_score_constants().green_score 20 | 21 | 22 | def yellow_score(): 23 | return get_score_constants().yellow_score 24 | 25 | 26 | def red_score(): 27 | return get_score_constants().red_score 28 | 29 | 30 | def ry_boundary(): 31 | return get_score_constants().ry_boundary 32 | 33 | 34 | def yg_boundary(): 35 | return get_score_constants().yg_boundary 36 | 37 | 38 | def score_to_colour(score): 39 | """ 40 | Return string containing colour based for score provided 41 | 42 | @arg score Health Score of project 43 | @returns "red", "green" or "yellow" 44 | """ 45 | 46 | if score is None: 47 | return 'gray' 48 | 49 | if score > yg_boundary(): 50 | return 'green' 51 | 52 | elif score > ry_boundary(): 53 | return 'yellow' 54 | 55 | else: 56 | return 'red' 57 | 58 | 59 | __all__ = [ 60 | 'green_score', 61 | 'yellow_score', 62 | 'red_score', 63 | 'ry_boundary', 64 | 'yg_boundary', 65 | 'score_to_colour' 66 | ] 67 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/metrics/lookup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from collections import namedtuple 5 | 6 | registered_algorithms = {} 7 | MetricAlgoEntry = namedtuple('MetricAlgoEntry', ['fn', 'cls']) 8 | 9 | 10 | def metric(cls): 11 | global registered_algorithms 12 | registered_algorithms[cls.__name__] = MetricAlgoEntry(cls.as_metric(), cls) 13 | return cls 14 | 15 | 16 | def get_metricalgo(metricname): 17 | return registered_algorithms[metricname].fn 18 | 19 | 20 | def get_metriccls(metricname): 21 | return registered_algorithms[metricname].cls 22 | 23 | 24 | def get_metricunit(metricname, value): 25 | if value is None: 26 | return '' 27 | 28 | cls = get_metriccls(metricname) 29 | 30 | unit = getattr(cls, 'unit', None) 31 | 32 | # Check for plural 33 | if value != 1 and unit is not None: 34 | unit = getattr(cls, 'unit_plural', unit + 's') 35 | 36 | # Set to empty string if no unit 37 | if unit is None: 38 | unit = '' 39 | 40 | # Prepend space to unit unless percentage 41 | return ' ' + unit if unit and unit != '%' else unit 42 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/hmeter_frontend/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/chaoss-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/hmeter_frontend/static/chaoss-logo.png -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/about.css: -------------------------------------------------------------------------------- 1 | figure { 2 | text-align: center; 3 | } 4 | 5 | figcaption { 6 | font-weight: bold; 7 | font-size: 1.1em; 8 | } 9 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/all-projects.less: -------------------------------------------------------------------------------- 1 | @import "common.less"; 2 | 3 | .form.search-form { 4 | display: block; 5 | margin-bottom: @line-height-computed; 6 | 7 | input[type=search].inactive { 8 | color: lighten(@input-color, 25%); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/bootstrap: -------------------------------------------------------------------------------- 1 | ../../../../../libs/bootstrap/less/ -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/bootstrap-slider.css: -------------------------------------------------------------------------------- 1 | ../../../../../libs/bootstrap-slider/css/bootstrap-slider.css -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/business-units.less: -------------------------------------------------------------------------------- 1 | .collapsable-projects .project-search { 2 | display: none; 3 | } 4 | 5 | [data-toggle="collapse"] { 6 | cursor: pointer; 7 | 8 | .caret { 9 | margin-right: 2px; 10 | border-top: 4px solid transparent; 11 | border-bottom: 4px solid transparent; 12 | border-right: 4px solid transparent; 13 | border-left: 4px solid; 14 | } 15 | 16 | &[aria-expanded="true"] .caret { 17 | border-top: 4px solid; 18 | border-bottom: 4px solid transparent; 19 | border-right: 4px solid transparent; 20 | border-left: 4px solid transparent; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/common.less: -------------------------------------------------------------------------------- 1 | @import "bootstrap/bootstrap.less"; 2 | @import "bootstrap/theme.less"; 3 | 4 | @line-height-base: 1.6180339887; 5 | 6 | #chaoss-logo { 7 | height: 2em; 8 | display: inline-block; 9 | margin-right: (@grid-gutter-width / 2 - 8px); 10 | vertical-align: text-bottom; 11 | } 12 | 13 | .navbar-brand { 14 | margin-top: 0; 15 | margin-bottom: 7px; 16 | 17 | span { 18 | display: inline-block; 19 | border-left: 2px solid @gray-light; 20 | padding-left: (@grid-gutter-width / 2); 21 | } 22 | } 23 | 24 | a.navbar-brand + .navbar-toggle { 25 | margin-top: 15px; 26 | margin-bottom: 0; 27 | } 28 | 29 | .navbar-nav > li.btn-migration > a { 30 | .text-danger; 31 | font-weight: bold; 32 | } 33 | 34 | main > section { 35 | > h1 { 36 | &:extend(.page-header); 37 | } 38 | 39 | > section { 40 | > h1 { 41 | &:extend(.h2); 42 | } 43 | 44 | > section { 45 | > h1 { 46 | &:extend(.h3); 47 | } 48 | 49 | > section { 50 | > h1 { 51 | &:extend(.h4); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | .health-scorebar.progress { 59 | height: 0.6em; 60 | margin: 0; 61 | } 62 | 63 | #feedback-modal { 64 | #feedback-form { 65 | label { 66 | .make-sm-column (2); 67 | } 68 | 69 | label + * { 70 | .make-sm-column (10); 71 | } 72 | } 73 | 74 | i.fa.fa-spinner { 75 | display: none; 76 | } 77 | 78 | .in-progress i.fa.fa-spinner { 79 | display: inline-block; 80 | } 81 | } 82 | 83 | .ellipsize { 84 | text-overflow: ellipsis; 85 | white-space: nowrap; 86 | overflow: hidden; 87 | } 88 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/project-compare.css: -------------------------------------------------------------------------------- 1 | body.dragging { 2 | cursor: move !important; 3 | } 4 | 5 | table { 6 | margin-top: 6px; 7 | table-layout: fixed; 8 | border-spacing: 0; 9 | min-width: 100%; 10 | } 11 | 12 | /* column width */ 13 | .metric { 14 | min-width: 40ex; 15 | } 16 | 17 | th.project { 18 | min-width: 15ex; 19 | text-align: center; 20 | } 21 | 22 | colgroup.project-columns { 23 | width: 60%; 24 | } 25 | 26 | /* project headers */ 27 | th.placeholder { 28 | position: relative; 29 | display: block; 30 | width: 0; 31 | margin: 0; 32 | padding: 0; 33 | } 34 | 35 | th.placeholder:before { 36 | content: ""; 37 | position: absolute; 38 | display: block; 39 | width: 0; 40 | height: 0; 41 | border: 5px solid transparent; 42 | border-top-color: red; 43 | top: -6px; 44 | left: -6px; 45 | border-bottom: none; 46 | } 47 | 48 | .dragged { 49 | position: absolute; 50 | opacity: 0.5; 51 | z-index: 999; 52 | } 53 | 54 | /* paddings for metric levels */ 55 | tr[data-level="0"] > td.metric { 56 | padding-left: 0; 57 | } 58 | 59 | tr[data-level="1"] > td.metric { 60 | padding-left: 2em; 61 | } 62 | 63 | tr[data-level="2"] > td.metric { 64 | padding-left: 4em; 65 | } 66 | 67 | tr[data-level="3"] > td.metric { 68 | padding-left: 6em; 69 | } 70 | 71 | tr[data-level="4"] > td.metric { 72 | padding-left: 8em; 73 | } 74 | 75 | td { 76 | text-align: center; 77 | padding: 0 2em; 78 | } 79 | 80 | td.metric { 81 | text-align: left; 82 | } 83 | 84 | tbody.metric-scorebars td:first-child { 85 | padding: 0; 86 | } 87 | 88 | tr:hover { 89 | background-color: #eee; 90 | } 91 | 92 | td:first-child, th:first-child, th:first-child + th[data-project-id] { 93 | border-top-left-radius: 1ex; 94 | border-bottom-left-radius: 1ex; 95 | } 96 | 97 | td:last-child, th:last-child { 98 | border-top-right-radius: 1ex; 99 | border-bottom-right-radius: 1ex; 100 | } 101 | 102 | /* background colours */ 103 | tr th+th { 104 | background-color: #ccc; 105 | } 106 | 107 | tbody.graph-rows tr:nth-child(odd) { 108 | background-color: #eee; 109 | } 110 | 111 | tbody.metric-rows tr.level1 { 112 | background-color: #eee; 113 | } 114 | 115 | .health-scorebar { 116 | width: 100%; 117 | } 118 | 119 | /* expandable metrics */ 120 | tr.collapsible { 121 | cursor: pointer; 122 | } 123 | 124 | tr.collapsible > td:first-child:before { 125 | content: "▾ "; 126 | } 127 | 128 | tr.collapsible.collapsed > td:first-child:before { 129 | content: "▶ "; 130 | } 131 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/css/project-problems.css: -------------------------------------------------------------------------------- 1 | li:target { 2 | border-radius: 0.4em; 3 | background-color: #d9d9d9; 4 | border: 1px solid #a3a3a3; 5 | } 6 | 7 | #project-problems > li { 8 | padding: 0.4em; 9 | } 10 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/jquery-treeview: -------------------------------------------------------------------------------- 1 | ../../../../libs/jquery-treeview -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/all-projects-search.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | $(function () { 5 | var search_field = $(".search-form input[type=search]"); 6 | var target = search_field.closest ('form').next ('ol'); 7 | var orig_text = search_field.attr ('value'); 8 | 9 | search_field 10 | .on ("focus", function () { 11 | if ($(this).val () == orig_text) 12 | $(this).val ('').removeClass ('inactive'); 13 | }) 14 | .on ("blur", function () { 15 | if ($(this).val () == '') 16 | $(this).val (orig_text).addClass ('inactive'); 17 | }) 18 | .on ("keyup change", filter_projects); 19 | 20 | function filter_projects () 21 | { 22 | var term = search_field.val (); 23 | term = (term == orig_text) ? '' : term; 24 | 25 | if (!term) 26 | target.children ().show (); 27 | 28 | else { 29 | var terms = term.split (/[^a-zA-Z0-9]/); 30 | var selector = target.children (); 31 | 32 | for (var i=0; i"); 6 | }); 7 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/feedback.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | $(function () { 5 | var form = $("#feedback-form"); 6 | var dialog = form.closest (".modal-dialog"); 7 | 8 | function submit_form () {form.submit ();} 9 | function close_form () {dialog.closest (".modal").modal ("hide");} 10 | 11 | function focus_form () 12 | { 13 | form.find ("input[type=text], textarea").first ().focus (); 14 | } 15 | 16 | function clear_errors () 17 | { 18 | form.find (".has-error") 19 | .attr ("title", "") 20 | .removeClass ("has-error") 21 | .tooltip ("destroy"); 22 | } 23 | 24 | function reset_form () 25 | { 26 | form[0].reset (); 27 | clear_errors (); 28 | set_inprogress (false); 29 | } 30 | 31 | function set_inprogress (inprogress) { 32 | dialog[inprogress ? 'addClass' : 'removeClass']('in-progress'); 33 | } 34 | 35 | function is_inprogress () { 36 | return dialog.hasClass ('in-progress'); 37 | } 38 | 39 | form.closest (".modal-content").find ("button[type=submit]") 40 | .on ("click", submit_form); 41 | 42 | form.on ("submit", function (e) { 43 | e.preventDefault (); 44 | 45 | if (is_inprogress ()) 46 | return; 47 | 48 | set_inprogress (true); 49 | 50 | $.ajax ({ 51 | data: $(this).serialize (), 52 | type: $(this).attr ("method"), 53 | url: $(this).attr ("action"), 54 | 55 | success: close_form, 56 | 57 | error: function (xhr, status, error) { 58 | if (status == 'timeout') 59 | // try again 60 | submit_form (); 61 | 62 | else if (status == 'error' && xhr.status == 400) { 63 | var response = JSON.parse (xhr.responseText); 64 | clear_errors (); 65 | 66 | for (var field in response.errors) { 67 | form.find ("[name=" + field + "]") 68 | .closest (".form-group") 69 | .addClass ("has-error") 70 | .attr ('title', response.errors[field]) 71 | .tooltip ({'placement': 'right'}); 72 | } 73 | } else { 74 | alert ("Unknown error while submitting feedback: " + 75 | error); 76 | } 77 | }, 78 | 79 | complete: set_inprogress.bind (null, false) 80 | }); 81 | }); 82 | 83 | dialog.closest (".modal") 84 | .on ("hidden.bs.modal", reset_form) 85 | .on ("shown.bs.modal", focus_form); 86 | }); 87 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/jquery-icontains.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | $.expr[":"].icontains = $.expr.createPseudo (function (arg) { 5 | return function (elem) { 6 | return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0; 7 | }; 8 | }); 9 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/jquery-sortable.js: -------------------------------------------------------------------------------- 1 | ../../../../../libs/jquery-sortable/source/js/jquery-sortable.js -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/jquery.flot.canvas.min.js: -------------------------------------------------------------------------------- 1 | (function(f){var c={canvas:true};var b,e,d;var a=Object.prototype.hasOwnProperty;function g(j,i){var h=i.Canvas;if(b==null){e=h.prototype.getTextInfo,d=h.prototype.addText,b=h.prototype.render}h.prototype.render=function(){if(!j.getOptions().canvas){return b.call(this)}var l=this.context,k=this._textCache;l.save();l.textBaseline="middle";for(var w in k){if(a.call(k,w)){var u=k[w];for(var t in u){if(a.call(u,t)){var n=u[t],o=true;for(var v in n){if(a.call(n,v)){var m=n[v],r=m.positions,y=m.lines;if(o){l.fillStyle=m.font.color;l.font=m.font.definition;o=false}for(var q=0,s;s=r[q];q++){if(s.active){for(var p=0,x;x=s.lines[p];p++){l.fillText(y[p].text,x[0],x[1])}}else{r.splice(q--,1)}}if(r.length==0){delete n[v]}}}}}}}l.restore()};h.prototype.getTextInfo=function(t,w,o,r,l){if(!j.getOptions().canvas){return e.call(this,t,w,o,r,l)}var m,u,p,n;w=""+w;if(typeof o==="object"){m=o.style+" "+o.variant+" "+o.weight+" "+o.size+"px "+o.family}else{m=o}u=this._textCache[t];if(u==null){u=this._textCache[t]={}}p=u[m];if(p==null){p=u[m]={}}n=p[w];if(n==null){var k=this.context;if(typeof o!=="object"){var q=f("
 
").css("position","absolute").addClass(typeof o==="string"?o:null).appendTo(this.getTextLayer(t));o={lineHeight:q.height(),style:q.css("font-style"),variant:q.css("font-variant"),weight:q.css("font-weight"),family:q.css("font-family"),color:q.css("color")};o.size=q.css("line-height",1).height();q.remove()}m=o.style+" "+o.variant+" "+o.weight+" "+o.size+"px "+o.family;n=p[w]={width:0,height:0,positions:[],lines:[],font:{definition:m,color:o.color}};k.save();k.font=m;var y=(w+"").replace(/
|\r\n|\r/g,"\n").split("\n");for(var s=0;sl){l=k[j]}}return l+1}function c(m){var l=[];for(var k in m.categories){var j=m.categories[k];if(j>=m.min&&j<=m.max){l.push([j,k])}}l.sort(function(o,n){return o[0]-n[0]});return l}function i(k,m,n){if(k[m].options.mode!="categories"){return}if(!k[m].categories){var q={},p=k[m].options.categories||{};if(d.isArray(p)){for(var l=0;l=h.length){return null}return h[j.fillBetween]}return null}function e(B,v,g){if(v.fillBetween==null){return}var n=d(v,B.getData());if(!n){return}var y=g.pointsize,E=g.points,t=n.datapoints.pointsize,H=n.datapoints.points,p=[],w,u,q,G,F,k,r=v.lines.show,o=y>2&&g.format[2].y,h=r&&v.lines.steps,D=true,C=0,A=0,z,x;while(true){if(C>=E.length){break}z=p.length;if(E[C]==null){for(x=0;x=H.length){if(!r){for(x=0;xG){if(r&&C>0&&E[C-y]!=null){q=u+(E[C-y+1]-u)*(G-w)/(E[C-y]-w);p.push(G);p.push(q);for(x=2;x0&&H[A-t]!=null){k=F+(H[A-t+1]-F)*(w-G)/(H[A-t]-G)}C+=y}}D=false;if(z!==p.length&&o){p[z+2]=k}}}}if(h&&z!==p.length&&z>0&&p[z]!==null&&p[z]!==p[z-y]&&p[z+1]!==p[z-y+1]){for(x=0;x").load(l).error(l).attr("src",j)})};function e(q,o,f){var g=q.getPlotOffset();if(!f.images||!f.images.show){return}var v=f.datapoints.points,n=f.datapoints.pointsize;for(var r=0;rt){x=t;t=w;w=x}if(j>h){x=h;h=j;j=x}if(f.images.anchor=="center"){x=0.5*(t-w)/(y.width-1);w-=x;t+=x;x=0.5*(h-j)/(y.height-1);j-=x;h+=x}if(w==t||j==h||w>=k.max||t<=k.min||j>=u.max||h<=u.min){continue}var m=0,s=0,l=y.width,p=y.height;if(wk.max){l+=(l-m)*(k.max-t)/(t-w);t=k.max}if(ju.max){s+=(s-p)*(u.max-h)/(h-j);h=u.max}w=k.p2c(w);t=k.p2c(t);j=u.p2c(j);h=u.p2c(h);if(w>t){x=t;t=w;w=x}if(j>h){x=h;h=j;j=x}x=o.globalAlpha;o.globalAlpha*=f.images.alpha;o.drawImage(y,m,s,l-m,p-s,w+g.left,j+g.top,t-w,h-j);o.globalAlpha=x}}function b(i,f,g,h){if(!f.images.show){return}h.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function d(f){f.hooks.processRawData.push(b);f.hooks.drawSeries.push(e)}c.plot.plugins.push({init:d,options:a,name:"image",version:"1.1"})})(jQuery); -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/jquery.flot.ranges.min.js: -------------------------------------------------------------------------------- 1 | (function(e){var b=Number.MAX_VALUE;function f(i){i=+i;if(i==Infinity){return b}else{if(i==-Infinity){return -b}else{return i}}}function c(r,k,n,q){if(!k.ranges.show){return}var u=[];q.pointsize=4;q.points=[];q.format=[{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}];for(var m=0;m=0;a--){if(k[a]==this){k.splice(a,1);break}}f.removeData(g);if(!k.length){if(b){cancelAnimationFrame(w)}else{clearTimeout(w)}w=null}},add:function(l){if(!d[q]&&this[z]){return false}var h;function f(t,u,i){var o=$(this),m=o.data(g)||{};m.w=u!==y?u:o.width();m.h=i!==y?i:o.height();h.apply(this,arguments)}if($.isFunction(l)){h=l;return f}else{h=l.handler;l.handler=f}}};function p(e){if(b===true){b=e||1}for(var h=k.length-1;h>=0;h--){var a=$(k[h]);if(a[0]==v||a.is(":visible")){var i=a.width(),n=a.height(),m=a.data(g);if(m&&(i!==m.w||n!==m.h)){a.trigger(x,[m.w=i,m.h=n]);b=e||true}}else{m=a.data(g);m.w=0;m.h=0}}if(w!==null){if(b&&(e==null||e-b<1000)){w=v.requestAnimationFrame(p)}else{w=setTimeout(p,d[c]);b=false}}}if(!v.requestAnimationFrame){v.requestAnimationFrame=function(){return v.webkitRequestAnimationFrame||v.mozRequestAnimationFrame||v.oRequestAnimationFrame||v.msRequestAnimationFrame||function(e,a){return v.setTimeout(function(){e((new Date).getTime())},d[j])}}()}if(!v.cancelAnimationFrame){v.cancelAnimationFrame=function(){return v.webkitCancelRequestAnimationFrame||v.mozCancelRequestAnimationFrame||v.oCancelRequestAnimationFrame||v.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);(function(b){var a={};function c(f){function e(){var h=f.getPlaceholder();if(h.width()==0||h.height()==0){return}f.resize();f.setupGrid();f.draw()}function g(i,h){i.getPlaceholder().resize(e)}function d(i,h){i.getPlaceholder().unbind("resize",e)}f.hooks.bindEvents.push(g);f.hooks.shutdown.push(d)}b.plot.plugins.push({init:c,options:a,name:"resize",version:"1.0"})})(jQuery); -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/jquery.flot.stack.min.js: -------------------------------------------------------------------------------- 1 | (function(b){var a={series:{stack:null}};function c(f){function d(k,j){var h=null;for(var g=0;g2&&(H?g.format[2].x:g.format[2].y),h=t&&w.lines.steps,F=true,o=H?1:0,y=H?0:1,E=0,C=0,B,z;while(true){if(E>=G.length){break}B=q.length;if(G[E]==null){for(z=0;z=K.length){for(z=0;zJ){if(t&&E>0&&G[E-A]!=null){r=v+(G[E-A+y]-v)*(J-x)/(G[E-A+o]-x);q.push(J);q.push(r+I);for(z=2;z0&&K[C-u]!=null){k=I+(K[C-u+y]-I)*(x-J)/(K[C-u+o]-J)}q[B+y]+=k;E+=A}}F=false;if(B!=q.length&&p){q[B+2]+=k}}}}if(h&&B!=q.length&&B>0&&q[B]!=null&&q[B]!=q[B-A]&&q[B+1]!=q[B-A+1]){for(z=0;z0&&j[E-z]!=null){var o=r+(u-q)*(r-j[E-z])/(q-j[E-z+1]);C.push(o);C.push(u);for(A=2;A0){var k=b.inArray(v,D.getData());D.getData().splice(k+1,0,t)}}function d(i,g,h){if(!g.threshold){return}if(g.threshold instanceof Array){g.threshold.sort(function(k,j){return k.below-j.below});b(g.threshold).each(function(j,k){f(i,g,h,k.below,k.color)})}else{f(i,g,h,g.threshold.below,g.threshold.color)}}e.hooks.processDatapoints.push(d)}b.plot.plugins.push({init:c,options:a,name:"threshold",version:"1.2"})})(jQuery); -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/jquery.tablesorter: -------------------------------------------------------------------------------- 1 | ../../../../../libs/tablesorter/ -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/project/compare/colourize.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | $(function () 5 | { 6 | $("td:not(.metric)").css ('color', 7 | function () { 8 | var score = +$(this) 9 | .attr ("data-health-score"); 10 | 11 | return health_score_to_colour (score); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/project/compare/expandable-metrics.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | $.fn.expand = function expand () 5 | { 6 | return this.removeClass ("collapsed") 7 | .each (function () { 8 | var $this = $(this); 9 | 10 | if (!$this.has_child_rows ()) 11 | return; 12 | 13 | var level = $this.data ("level") 14 | 15 | var descendants = $this.nextUntil ("[data-level=" + level + "]"); 16 | descendants.each (function () { 17 | var $this = $(this); 18 | var parent = $this.parent_row (); 19 | 20 | while (parent.length) { 21 | if (parent.hasClass ("collapsed")) 22 | return; 23 | 24 | parent = parent.parent_row (); 25 | } 26 | 27 | // Did not find any ancestors with collaped class, so show 28 | $this.show (); 29 | }); 30 | }); 31 | } 32 | 33 | $.fn.collapse = function collapse () 34 | { 35 | return this.each (function () { 36 | var $this = $(this); 37 | 38 | if (!$this.has_child_rows ()) 39 | return; 40 | 41 | level = $this.data ("level"); 42 | console.log ("collapsing", this, 43 | $this.nextUntil ("[data-level=" + level + "]").hide ()); 44 | $this.addClass ("collapsed"); 45 | }); 46 | } 47 | 48 | $.fn.parent_row = function parent_row () 49 | { 50 | var level = this.data ("level"); 51 | var parent_row = this.data ("parent-row") || 52 | this.prevAll ("[data-level=" + (level-1) + "]").first (); 53 | 54 | // Cache it for future access 55 | this.data ("parent-row", parent_row); 56 | return parent_row; 57 | } 58 | 59 | $.fn.has_child_rows = function has_child_rows () 60 | { 61 | var $this = this.first (); 62 | if (!$this.length) 63 | return false; 64 | 65 | var this_level = $this.data ("level"); 66 | var next_level = $this.next ("[data-level]").data ("level"); 67 | 68 | return (this_level < next_level); 69 | } 70 | 71 | $(function () { 72 | // hide everything but the first level 73 | $("tbody.metric-rows tr[data-level]") 74 | .addClass (function () { 75 | return $(this).has_child_rows () ? "collapsible" : ""; 76 | }) 77 | .collapse () 78 | .click (function () { 79 | var $this = $(this); 80 | if ($this.hasClass ("collapsed")) 81 | $this.expand (); 82 | else 83 | $this.collapse (); 84 | }); 85 | $("tbody.metric-rows tr[data-level=0]").expand (); 86 | }); 87 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/project/compare/scorebars.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | $(function () { 5 | $(".health-scorebar").render_scorebar ("data-health-score"); 6 | }) 7 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/project/health-score-utils.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | function health_score_to_colour (score) 5 | { 6 | var hue = score * 1.2; 7 | return "hsl(" + hue + ",100%,40%)"; 8 | } 9 | 10 | // Render health score bars as progressbars 11 | $.fn.render_scorebar = function (attrname, colourize) 12 | { 13 | // default to colourizing the bar. 14 | colourize = (colourize === undefined) ? true : colourize; 15 | 16 | return this.each (function () 17 | { 18 | var $this = $(this); 19 | var value = +$this.attr (attrname); 20 | 21 | // not rendered yet. render now! 22 | if (!$this.hasClass ("progress")) { 23 | $this.addClass ("progress") 24 | .append ($("
", { 25 | 'class': 'progress-bar', 26 | 'role': 'progressbar', 27 | 'aria-valuemin': 0, 28 | 'aria-valuemax': 100 29 | })); 30 | } 31 | 32 | $this.find (".progress-bar") 33 | .css ('width', value + '%') 34 | .attr ('aria-value', value) 35 | 36 | .css ('background', 37 | colourize ? 38 | health_score_to_colour (value) : 39 | '#857979'); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/static/js/project/index/tablesorter.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Red Hat, Inc. 2 | License: GPLv3 or any later version */ 3 | 4 | // Custom parsers for sorting health-bar and date columns 5 | $.tablesorter.addParser ({ 6 | id: 'health-bar', 7 | is: function () {return false;}, 8 | format: function (value, table, node) 9 | { 10 | return +$(node).attr ("data-health-score"); 11 | }, 12 | type: 'numeric' 13 | }); 14 | 15 | // Copied from datejs, but hacked for firefox's crappy datejs support 16 | $.tablesorter.addParser ({ 17 | id: 'timestamp', 18 | is: function () {return false;}, 19 | format: function (value, table, node) { 20 | return +$(node).attr ("data-timestamp"); 21 | }, 22 | type: 'numeric' 23 | }); 24 | 25 | 26 | $(function () { 27 | var pagesize = 20; 28 | 29 | $('.projects-listing').each (function () { 30 | var table = $(this); 31 | var pager = $('
') 32 | .append ('
') 33 | .insertAfter (table); 34 | 35 | var form = $('
8 | 9 | 12 | 13 | {% endblock %} 14 | 15 | {% block before_aggregate_metrics %} 16 |

Reliability

17 |
    18 |
  • Number of bugs reported over last: 19 |
      20 | {% for months, count in bugs_opened_count_period %} 21 |
    • {{ months }} months: {{ count|intcomma }}
    • 22 | {% endfor %} 23 |
    24 |
  • 25 | 26 |
  • Number of bugs closed over last: 27 |
      28 | {% for months, count in bugs_closed_count_period %} 29 |
    • {{ months }} months: {{ count|intcomma }}
    • 30 | {% endfor %} 31 |
    32 |
  • 33 | 34 |
  • Number of severe bugs reported over last: 35 |
      36 | {% for months, count in severe_bugs_count_period %} 37 |
    • {{ months }} months: {{ count|intcomma }}
    • 38 | {% endfor %} 39 |
    40 |
  • 41 |
42 | 43 |

Size:

44 |
    45 |
  • Lines of code over last: 46 |
      47 | {% for months, sloc in sloc_count_period %} 48 |
    • {{ months }} months: {{ sloc|intcomma }}
    • 49 | {% endfor %} 50 |
    51 |
  • 52 |
53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/fragments/detail/sections/mailing-list-activity.html: -------------------------------------------------------------------------------- 1 | {% extends "hmeter_frontend/project/fragments/detail/sections/base.html" %} 2 | {% load humanize %} 3 | 4 | {% block before_leaf_metrics %} 5 |
6 |
7 |
8 |
9 |
10 | 11 | 14 | {% endblock %} 15 | 16 | {% block before_aggregate_metrics %} 17 |

Number of emails over last:

18 |
    19 | {% for months, mails, posters in mail_count_period %} 20 |
  • 21 | {{ months }} months: 22 | {{ mails|intcomma }} post{{ mails|pluralize }} from 23 | {{ posters|intcomma }} poster{{ posters|pluralize }} 24 |
  • 25 | {% endfor %} 26 |
27 | 28 |

Top email address domains posting:

29 | 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/fragments/detail/sections/mailing-list-graphs.js: -------------------------------------------------------------------------------- 1 | (function () 2 | { 3 | $.when (window.graphready_deferred, 4 | $.getJSON ("{% url 'hmeter_frontend:project:mldata' id=project.id domain='' %}") 5 | {% if hldomain %}, 6 | $.getJSON ("{% url 'hmeter_frontend:project:mldata' id=project.id domain=hldomain %}") 7 | {% endif %}) 8 | .done (plot_emails); 9 | $.when (window.graphready_deferred, 10 | $.getJSON ("{% url 'hmeter_frontend:project:mlposterdata' id=project.id domain='' %}") 11 | {% if hldomain %}, 12 | $.getJSON ("{% url 'hmeter_frontend:project:mlposterdata' id=project.id domain=hldomain %}") 13 | {% endif %}) 14 | .done(plot_emailers); 15 | 16 | function plot_emails (_, mldata, hlmldata) 17 | { 18 | mldata = mldata[0]; 19 | hlmldata = hlmldata && hlmldata[0]; 20 | 21 | var email_freq = new FrequencySeries ("Emails", mldata, "#4069ED"); 22 | 23 | var series_list = hlmldata ? 24 | email_freq.split_by_domain (hlmldata, hldomain, hlcolor) : 25 | [email_freq]; 26 | 27 | (new AnnotatedGraph ($("#email_freq_graph"), 28 | series_list, 29 | "Emails per @AGGREGATOR@ over time")).draw (); 30 | 31 | var cumulative_series = $.map (series_list, function (x) { 32 | return new CumulativeSeries (x); 33 | }); 34 | 35 | (new AnnotatedGraph ($("#cumulative_emails_graph"), 36 | cumulative_series, 37 | "Total number of emails over time")).draw (); 38 | } 39 | 40 | function plot_emailers (_, emailers, hlemailers) 41 | { 42 | emailers = emailers[0]; 43 | hlemailers = hlemailers && hlemailers[0]; 44 | 45 | var poster_freq = new FrequencySeries ("Email posters", emailers, 46 | "#4069ED"); 47 | var series_list = hlemailers ? 48 | poster_freq.split_by_domain (hlemailers, hldomain, hlcolor) : 49 | [poster_freq]; 50 | 51 | $.each (series_list, function () 52 | { 53 | mixin (this, BarSeries); 54 | BarSeries.apply (this); 55 | }); 56 | 57 | (new AnnotatedGraph ($("#poster_freq_graph"), 58 | series_list, 59 | "Posters per @AGGREGATOR@ over time")).draw (); 60 | } 61 | }) (); 62 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/fragments/detail/sections/publicity.html: -------------------------------------------------------------------------------- 1 | {% extends "hmeter_frontend/project/fragments/detail/sections/tabbed.html" %} 2 | 3 | {% block after_aggregate_metrics %} 4 |
5 |

Downloads

6 |

Not currently determined

7 | Back to top 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/fragments/detail/sections/security.html: -------------------------------------------------------------------------------- 1 | {% extends "hmeter_frontend/project/fragments/detail/sections/base.html" %} 2 | {% load humanize %} 3 | 4 | {% block before_aggregate_metrics %} 5 |

Security:

6 |
    7 |
  • CVEs (Common Vulnerabilities and Exposures) filed over last: 8 |
      9 | {% if cpe_product_names %} 10 | {% for months, count in cve_count_period %} 11 |
    • {{ months }} months: {{ count|intcomma }}
    • 12 | {% endfor %} 13 | {% else %} 14 |
    • unknown (CPE needs to be configured)
    • 15 | {% endif %} 16 |
    17 |
  • 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/fragments/detail/sections/tabbed.html: -------------------------------------------------------------------------------- 1 | {% extends "hmeter_frontend/project/fragments/detail/sections/base.html" %} 2 | 3 | {% block before_aggregate_metrics %} 4 | 13 | {% endblock before_aggregate_metrics %} 14 | 15 | {% block aggregate_metrics %} 16 |
17 | {{ block.super }} 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/fragments/project-listing-table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% for metric in l1_metrics %} 7 | 8 | {% endfor %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for metric in l1_metrics %} 21 | 22 | {% endfor %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% for proj in projects %} 32 | 33 | 34 | 35 | 42 | 43 | {% for mc in proj.l1_scores %} 44 | 49 | {% endfor %} 50 | 51 | 59 | 60 | 68 | 69 | 73 | 74 | {% endfor %} 75 | 76 |
Project Name{{ metric.title }}Proj StartLast Activity
36 | 37 | 39 | {{ proj.name }} 40 | 41 | 45 |
47 |
48 |
53 | {% if proj.smart_start_date %} 54 | {{ proj.smart_start_date|date:"d-M-Y" }} 55 | {% else %} 56 | N/A 57 | {% endif %} 58 | 62 | {% if proj.last_activity %} 63 | {{ proj.last_activity|date:"d-M-Y" }} 64 | {% else %} 65 | N/A 66 | {% endif %} 67 | 70 | 72 |
77 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/graph-data.js: -------------------------------------------------------------------------------- 1 | {% for key, value in data.items %} 2 | window.{{ key }} = {{ value|safe }}; 3 | {% endfor %} 4 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/index.html: -------------------------------------------------------------------------------- 1 | {% extends "hmeter_frontend/base.html" %} 2 | 3 | {% load staticfiles %} 4 | {% load metrics jsonify %} 5 | 6 | {% block title %}Open Source Prospector: Project Listing{% endblock %} 7 | 8 | {% block css %} 9 | 11 | {% endblock css %} 12 | 13 | {% block uncompressed_js %} 14 | {{ block.super }} 15 | 18 | 21 | {% endblock uncompressed_js %} 22 | {% block js %} 23 | {{ block.super }} 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 | 48 | 51 | 54 | {% endblock js%} 55 | 56 | {% block content %} 57 |
58 |

Monitored Projects

59 | 60 | Alternate view of projects based on Business Units 61 | 62 | {% include "hmeter_frontend/project/fragments/project-listing-table.html" %} 63 |
64 | {% endblock%} 65 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/metric-history.html: -------------------------------------------------------------------------------- 1 | {% extends "hmeter_frontend/base.html" %} 2 | 3 | {% block title %} 4 | Open Source Prospector: {{ project.name }} score history 5 | {% endblock title %} 6 | 7 | {% block head_matter %} 8 | 10 | {% endblock head_matter %} 11 | 12 | {% block content %} 13 |
14 |

Score history for {{ project.name }}

15 | 16 |
17 | 18 | 19 | 20 | {% for date in dates %} 21 | 22 | {% endfor %} 23 | 24 | 25 | 26 | 27 | 28 | {% for date in dates %} 29 | 30 | {% endfor %} 31 | 32 | 33 | 34 | 35 | {% for metric in metricdata %} 36 | 37 | 38 | {% for line in metric.datalist %} 39 | 40 | {% endfor %} 41 | 42 | {% endfor %} 43 | 44 |
{{ date|date:"DATE_FORMAT" }}
{{ metric.title }}raw={{ line.raw_value }}, score={{ line.score }}
45 |
46 | 47 |
48 | {% endblock content %} 49 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templates/hmeter_frontend/project/problems.html: -------------------------------------------------------------------------------- 1 | {% extends "hmeter_frontend/base.html" %} 2 | 3 | {% block title %}Open Source Prospector: Problems{% endblock %} 4 | 5 | {% block head_matter %} 6 | 8 | {% endblock head_matter %} 9 | 10 | {% block content %} 11 |
12 |

Projects with Problems

13 |
    14 | {% for project, problems in project_problems %} 15 |
  • 16 | 17 | {{ project.name }} 18 | 19 | 20 |
      21 | {% for problem in problems %} 22 |
    • {{ problem }}
    • 23 | {% endfor %} 24 |
    25 |
  • 26 | {% endfor %} 27 |
28 |
29 | {% endblock content %} 30 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/hmeter_frontend/templatetags/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templatetags/divby.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django import template 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.filter 10 | def divby(dividend, divisor): 11 | return dividend / divisor 12 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templatetags/includeor.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django import template 5 | 6 | register = template.Library() 7 | 8 | 9 | class IncludeOrNode(template.Node): 10 | def __init__(self, template_name, nodelist): 11 | self.template_name = template_name 12 | self.nodelist = nodelist 13 | 14 | def render(self, context): 15 | try: 16 | tmp = template.loader.get_template( 17 | self.template_name.resolve(context)) 18 | except template.TemplateDoesNotExist: 19 | if 'commit-activity' in self.template_name.resolve(context): 20 | raise 21 | return self.nodelist.render(context) 22 | else: 23 | return tmp.render(context.flatten()) 24 | 25 | 26 | @register.tag 27 | def includeor(parser, token): 28 | tag_name, template_name = token.split_contents() 29 | template_name = parser.compile_filter(template_name) 30 | 31 | nodelist = parser.parse(('endincludeor',)) 32 | parser.delete_first_token() 33 | 34 | return IncludeOrNode(template_name, nodelist) 35 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templatetags/jsonify.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import json 5 | from django import template 6 | from django.utils.safestring import mark_safe 7 | 8 | register = template.Library() 9 | 10 | 11 | @register.filter 12 | def jsonify(obj): 13 | return mark_safe(json.dumps(obj)) 14 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templatetags/metrics.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django import template 5 | from django.contrib.humanize.templatetags.humanize import intcomma 6 | from django.template.defaultfilters import floatformat 7 | from django.utils.safestring import mark_safe 8 | import json 9 | 10 | from healthmeter.hmeter_frontend.metrics.lookup import get_metricunit 11 | 12 | register = template.Library() 13 | 14 | 15 | @register.filter 16 | def format_score(value): 17 | if value is None: 18 | return "N/A" 19 | 20 | if isinstance(value, bool): 21 | return '\u2714' if value else '\u2718' 22 | 23 | elif isinstance(value, int): 24 | return intcomma(value) 25 | 26 | f = floatformat(value, 2) 27 | if f: 28 | return f 29 | 30 | return value 31 | 32 | 33 | @register.filter 34 | def jsonify_score_tree(score, autoescape=None): 35 | def convert_score(score): 36 | ret = {'description': score.metric.description, 37 | 'title': score.metric.title, 38 | 'score': score.score, 39 | 'weight': score.normalized_weight, 40 | 'metric_id': score.metric.id, 41 | 'children': [convert_score(child) for child in score.children]} 42 | 43 | if score.metric.colour: 44 | ret['colour'] = score.metric.colour 45 | 46 | return ret 47 | 48 | return mark_safe(json.dumps(convert_score(score))) 49 | 50 | 51 | @register.filter 52 | def metric_unit(metric_result): 53 | return get_metricunit(metric_result.metric.algorithm.name, 54 | metric_result.raw_value) 55 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/templatetags/recurse.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django import template 5 | 6 | register = template.Library() 7 | TemplateSyntaxError = template.TemplateSyntaxError 8 | 9 | 10 | class RecurseNode(template.Node): 11 | def __init__(self, params): 12 | self.params = params 13 | 14 | def render(self, context): 15 | try: 16 | recurse_ctx = context['_recurse_ctx'] 17 | except KeyError: 18 | raise TemplateSyntaxError("{0} found outside recursion context") 19 | 20 | args = [param.resolve(context) for param in self.params] 21 | return recurse_ctx._render(context, *args) 22 | 23 | 24 | class RecurseDefinitionNode(template.Node): 25 | def __init__(self, params, nodelist): 26 | self.params = params 27 | self.nodelist = nodelist 28 | 29 | def _render(self, context, *args): 30 | context.push() 31 | 32 | context['level'] = context.get('level', -1) + 1 33 | context['_recurse_ctx'] = self 34 | 35 | try: 36 | if args and len(args) != len(self.params): 37 | raise IndexError 38 | 39 | for i, arg in enumerate(args): 40 | context[self.params[i]] = arg 41 | 42 | except IndexError: 43 | raise TemplateSyntaxError("Number of arguments passed to recurse " 44 | "do not match defrecurse") 45 | 46 | output = self.nodelist.render(context) 47 | 48 | context.pop() 49 | 50 | return output 51 | 52 | def render(self, context): 53 | return self._render(context) 54 | 55 | 56 | @register.tag 57 | def defrecurse(parser, token): 58 | """ 59 | Recursively render things in Django templates. 60 | 61 | Usage: 62 | {% defrecurse param1 param2... %} 63 |
    64 | {% for x in param.children %} 65 |
  • {{ x }} 66 | {% recurse param1 param2... %} 67 |
  • 68 | {% endfor %} 69 |
70 | {% enddefrecurse %} 71 | """ 72 | params = token.split_contents() 73 | tag_name = params.pop(0) 74 | 75 | nodelist = parser.parse(('enddefrecurse',)) 76 | parser.delete_first_token() 77 | 78 | return RecurseDefinitionNode(params, nodelist) 79 | 80 | 81 | @register.tag 82 | def recurse(parser, token): 83 | params = token.split_contents() 84 | tag_name = params.pop(0) 85 | 86 | params = [parser.compile_filter(param) for param in params] 87 | 88 | return RecurseNode(params) 89 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .compression import * 5 | from .containers import * 6 | from .filetools import * 7 | from .mail import * 8 | from .path import * 9 | from .web import * 10 | from .cache import * 11 | from .stats import * 12 | from .misc import * 13 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/containers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | class AttrDict(dict): 5 | __getattr__ = dict.__getitem__ 6 | __setattr__ = dict.__setattr__ 7 | 8 | 9 | def filterdict(d, blacklist=None, whitelist=None): 10 | """ 11 | Create a new dict with keys either present in blacklist or not present in 12 | whitelist filtered out. 13 | """ 14 | keys = set(d.iterkeys()) 15 | if whitelist is not None: 16 | keys &= set(whitelist) 17 | 18 | if blacklist is not None: 19 | keys ^= set(blacklist) 20 | 21 | return dict((k, v) for k, v in d.iteritems() if k in keys) 22 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/filetools.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import io 5 | 6 | 7 | class ChainFile(object): 8 | """ 9 | Virtual file object that acts like itertools.chain() 10 | """ 11 | def __init__(self, *files): 12 | self.files = list(files) 13 | 14 | def read(self, size=-1): 15 | if size < 0: 16 | return ''.join(f.read() for f in self.files) 17 | 18 | buf = io.StringIO() 19 | while self.files and size > 0: 20 | s = self.files[0].read(size) 21 | 22 | if len(s) < size: # EOF 23 | self.files.pop(0).close() 24 | 25 | size -= len(s) 26 | 27 | buf.write(s) 28 | 29 | return buf.getvalue() 30 | 31 | 32 | __all__ = ['ChainFile'] 33 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/iterators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | def intervals(iterable): 5 | prev_value = None 6 | for x in iterable: 7 | if prev_value is not None: 8 | yield x - prev_value 9 | prev_value = x 10 | 11 | 12 | def filternone(iterable): 13 | return (i for i in iterable if i is not None) 14 | 15 | 16 | def uniq(iterable, key=None): 17 | if key is None: 18 | key = lambda x: x 19 | 20 | prev_key = float('nan') 21 | for i in iterable: 22 | current_key = key(i) 23 | if current_key != prev_key: 24 | yield i 25 | prev_key = current_key 26 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/misc.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | def coerce_unicode(string): 5 | """ 6 | Anti-boilerplate function that coerces a string object into a unicode 7 | object. Does nothing if string is unicode to begin with. 8 | """ 9 | if isinstance(string, str): 10 | return str(string) 11 | 12 | # Assume string is a str (or pretending to be) instead 13 | return string.decode('utf8', 'replace') 14 | 15 | 16 | __all__ = [ 17 | 'coerce_unicode' 18 | ] 19 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/path.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import os 5 | 6 | 7 | def splitext_full(filename): 8 | """ 9 | Extracts full extension from filename. e.g. foo.tar.gz -> tar.gz. Supports 10 | up to 2 extension components. 11 | 12 | @arg filename File name to parse 13 | @return Parsed extension 14 | """ 15 | filename2, ext1 = os.path.splitext(filename) 16 | _, ext2 = os.path.splitext(filename2) 17 | 18 | if ext2: 19 | return ext2 + ext1 20 | 21 | else: 22 | return ext1 23 | 24 | 25 | __all__ = ['splitext_full'] 26 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/stats.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .iterators import filternone 5 | 6 | 7 | def weighted_average(iterable): 8 | """ 9 | Calculates a weighted average of the (value, weight) tuples in 10 | iterable. 11 | 12 | @arg iterable that yields (value, weight) tuples 13 | @return weighted average of values 14 | """ 15 | numerator = 0 16 | denominator = 0 17 | for value, weight in iterable: 18 | numerator += value * weight 19 | denominator += weight 20 | 21 | return float(numerator) / denominator 22 | 23 | 24 | def __or_none_helper(fn, *args): 25 | if len(args) == 1 and hasattr(args[0], '__iter__'): 26 | iterable = args[0] 27 | else: 28 | iterable = args 29 | 30 | try: 31 | return fn(filternone(iterable)) 32 | except ValueError: 33 | return None 34 | 35 | 36 | def max_or_none(*args): 37 | """ 38 | Proxy for max() that allows for empty iterable to be passed. 39 | """ 40 | return __or_none_helper(max, *args) 41 | 42 | 43 | def min_or_none(*args): 44 | return __or_none_helper(min, *args) 45 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/utils/web.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import bs4 5 | import logging 6 | import urllib.request 7 | import urllib.parse 8 | 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class LinkScraper(object): 14 | def __init__(self, url, patterns, max_depth=-1): 15 | self.url = url 16 | self.parsed_url = urllib.parse.urlparse(url) 17 | self.patterns = patterns 18 | self.max_depth = max_depth 19 | 20 | logger.info('Initialized link scraper for %s', self.url) 21 | 22 | def __iter__(self): 23 | return self.get_links() 24 | 25 | def get_links(self): 26 | response = urllib.request.urlopen(self.url) 27 | bs = bs4.BeautifulSoup(response) 28 | 29 | for link in bs.find_all('a'): 30 | try: 31 | href = link['href'] 32 | 33 | except KeyError: 34 | # this happens for tags without a href attribute 35 | continue 36 | 37 | url = urllib.parse.urljoin(self.url, href) 38 | purl = urllib.parse.urlparse(url) 39 | 40 | # yield matching patterns immediately 41 | for i in self.patterns: 42 | if i.search(href): 43 | yield url 44 | break 45 | 46 | else: 47 | if ((not purl.path.startswith(self.parsed_url.path) or 48 | self.max_depth == 0 or 49 | purl.path == self.parsed_url.path)): 50 | continue 51 | 52 | for link in LinkScraper(url, self.patterns, 53 | self.max_depth - 1): 54 | yield link 55 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import healthmeter.hmeter_frontend.metrics.algorithms 5 | from . import feedback, project, static, business_units, all_projects 6 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/all_projects/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.views.generic import TemplateView 5 | from collections import namedtuple 6 | import os 7 | import glob 8 | import json 9 | from itertools import chain 10 | from healthmeter.hmeter_frontend.models import Project 11 | from healthmeter.hmeter_frontend.utils.iterators import uniq 12 | 13 | 14 | ProjectInfo = namedtuple('ProjectInfo', ['name', 'external', 'link', 'id']) 15 | 16 | 17 | class AllProjectsView(TemplateView): 18 | template_name = 'hmeter_frontend/all-projects.html' 19 | 20 | def get_context_data(self, **kwargs): 21 | data = super(AllProjectsView, self).get_context_data(**kwargs) 22 | 23 | json_files = glob.glob( 24 | os.path.join(os.path.dirname(__file__), '*.json')) 25 | 26 | def gen_project_lists(): 27 | for f in map(open, json_files): 28 | with f: 29 | yield json.load(f) 30 | 31 | project_keyfn = lambda x: x[0].lower() 32 | projects = uniq( 33 | sorted(chain(*gen_project_lists()), key=project_keyfn), 34 | key=project_keyfn 35 | ) 36 | 37 | treeid_lookup = dict((p.tree_id, p.id) 38 | for p in Project.objects.filter(parent=None)) 39 | 40 | djprojects = dict( 41 | (p.name.lower(), treeid_lookup[p.tree_id]) 42 | for p in Project.objects.all() 43 | ) 44 | 45 | def gen_projects(): 46 | for project, link in projects: 47 | try: 48 | id_ = djprojects[project.lower()] 49 | 50 | except KeyError: 51 | id_ = None 52 | 53 | yield ProjectInfo(project, id_ is None, link, id_) 54 | 55 | data['projects'] = list(gen_projects()) 56 | 57 | return data 58 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/business_units.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.views.generic import TemplateView 5 | import itertools 6 | 7 | from healthmeter.utils import ProxyObject 8 | from healthmeter.hmeter_frontend.models import Project, Metric 9 | from healthmeter.projectinfo.models import BusinessUnit 10 | 11 | 12 | class BusinessUnitIndex(TemplateView): 13 | template_name = 'hmeter_frontend/business-units.html' 14 | 15 | def get_context_data(self, **kwargs): 16 | data = super(BusinessUnitIndex, self).get_context_data(**kwargs) 17 | business_units = BusinessUnit.objects.prefetch_related('products') 18 | projects = Project.objects.filter(parent__isnull=True) \ 19 | .order_by('business_unit') \ 20 | .prefetch_dates() \ 21 | .with_l1_scores() 22 | 23 | bu_index = dict( 24 | (buid, list(projs)) 25 | for buid, projs in 26 | itertools.groupby(projects, lambda p: p.business_unit_id)) 27 | 28 | data['descriptions'] = dict((project.id, project.description) 29 | for project in projects) 30 | 31 | def get_products(bu): 32 | # sort locally because of prefetch 33 | for product in sorted(bu.products.all(), key=lambda p: p.name): 34 | if product.name == 'JBoss BRMS': 35 | yield ProxyObject(product, 36 | projects=bu_index.get(bu.id, None)) 37 | else: 38 | yield product 39 | 40 | data['business_units'] = [ 41 | ProxyObject(bu, 42 | root_projects=bu_index.get(bu.id, None), 43 | products=get_products(bu)) 44 | for bu in business_units 45 | ] 46 | data['unassociated_projects'] = bu_index[None] 47 | 48 | data['l1_metrics'] = Metric.objects.filter(level=1).order_by('lft') 49 | 50 | return data 51 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/feedback.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.conf import settings 5 | from django.core.mail import send_mail 6 | from django.http import HttpResponse, HttpResponseBadRequest 7 | from django.views.generic import View 8 | from django.views.decorators.csrf import csrf_exempt 9 | 10 | from healthmeter.views.generic import JsonView 11 | from healthmeter.hmeter_frontend.forms import FeedbackForm 12 | import json 13 | 14 | 15 | class FeedbackView(View): 16 | @classmethod 17 | def as_view(cls, *args, **kwargs): 18 | return csrf_exempt(super().as_view(*args, **kwargs)) 19 | 20 | @staticmethod 21 | def format_email(name, email): 22 | return '{0} <{1}>'.format(name, email) 23 | 24 | def post(self, request, *args, **kwargs): 25 | form = FeedbackForm(request.POST) 26 | 27 | if not form.is_valid(): 28 | respdata = { 29 | 'status': 'fail', 30 | 'errors': form.errors 31 | } 32 | responsecls = HttpResponseBadRequest 33 | 34 | else: 35 | respdata = { 36 | 'status': 'ok', 37 | 'errors': {} 38 | } 39 | responsecls = HttpResponse 40 | recipients = [self.format_email(name, email) 41 | for name, email in settings.MANAGERS] 42 | 43 | message = '{0}\n-- \nSent from: {1}\n'.format( 44 | form.cleaned_data['message'], 45 | request.META['HTTP_REFERER']) 46 | 47 | send_mail(subject='[OSP feedback] ' + form.cleaned_data['subject'], 48 | message=message, 49 | from_email=self.format_email( 50 | form.cleaned_data['name'], 51 | form.cleaned_data['email']), 52 | recipient_list=recipients) 53 | 54 | return responsecls(json.dumps(respdata), 55 | content_type='application/json') 56 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/project/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .index import * 5 | from .detail import * 6 | from .graphs import * 7 | from .problems import * 8 | from .compare import * 9 | from .metrichistory import * 10 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/project/index.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import collections 5 | from django.db.models import Max, Min 6 | from django.views.generic import ListView 7 | from django.utils.safestring import mark_safe 8 | import json 9 | 10 | from healthmeter.utils import djcached, ProxyObject 11 | from healthmeter.hmeter_frontend.utils.stats import max_or_none, min_or_none 12 | import itertools 13 | from ...models import Project, Metric 14 | 15 | 16 | class ProjectIndex(ListView): 17 | model = Project 18 | template_name = 'hmeter_frontend/project/index.html' 19 | context_object_name = 'projects' 20 | 21 | def get_context_data(self, *args, **kwargs): 22 | data = super().get_context_data(*args, **kwargs) 23 | 24 | data['l1_metrics'] = Metric.objects.filter(level=1).order_by('lft') 25 | data['descriptions'] = dict((project.id, project.description) 26 | for project in data['projects']) 27 | 28 | return data 29 | 30 | @djcached("project:roots_list", 3600) 31 | def get_queryset(self): 32 | return Project.objects.filter(parent__isnull=True) \ 33 | .order_by('name') \ 34 | .prefetch_dates() \ 35 | .with_l1_scores() 36 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/project/metrichistory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import collections 5 | from django.http import Http404 6 | from django.views.generic import DetailView 7 | from healthmeter.hmeter_frontend.models import Project, MetricCache, Metric 8 | 9 | 10 | MetricData = collections.namedtuple('MetricData', ['value', 'score']) 11 | MetricLine = collections.namedtuple('MetricLine', 12 | ['title', 'level', 'datalist']) 13 | 14 | 15 | class MetricHistoryView(DetailView): 16 | template_name = 'hmeter_frontend/project/metric-history.html' 17 | context_object_name = 'project' 18 | model = Project 19 | 20 | def get_context_data(self, **kwargs): 21 | data = super().get_context_data(**kwargs) 22 | 23 | base_qs = MetricCache.objects.filter(project=self.object, 24 | end__isnull=False) 25 | data['dates'] = base_qs.values_list('end', flat=True) \ 26 | .order_by('end') \ 27 | .distinct() 28 | 29 | data['metricdata'] = base_qs.iter_by_metric( 30 | ['level', 'title'], 31 | ['raw_value', 'score']) 32 | 33 | return data 34 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/project/problems.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.views.generic import ListView 5 | from healthmeter.hmeter_frontend.models import Project 6 | from healthmeter.utils import djcached 7 | 8 | 9 | class ProblematicProjects(ListView): 10 | template_name = 'hmeter_frontend/project/problems.html' 11 | context_object_name = 'project_problems' 12 | 13 | def iter_project_problems(self): 14 | for project in Project.objects.filter(parent=None).iterator(): 15 | problems = [] 16 | 17 | if project.is_wip: 18 | problems.append("Marked as work in progress") 19 | 20 | resources = ( 21 | (project.all_vcs_repositories, 'VCS commits', {}), 22 | (project.all_bug_trackers, 'bug reports', {}), 23 | (project.all_mailing_lists, 'mailing list posts', {}), 24 | (project.all_blogs, 'blogs', {}), 25 | ) 26 | 27 | for res, desc, kwargs in resources: 28 | if res.filter(last_updated=None, **kwargs).exists(): 29 | problems.append("Import of {0} not complete".format(desc)) 30 | 31 | if problems: 32 | yield project, problems 33 | 34 | @djcached('problematic_projects', 6 * 60 * 60) 35 | def get_queryset(self): 36 | return list(self.iter_project_problems()) 37 | -------------------------------------------------------------------------------- /wsgi/healthmeter/hmeter_frontend/views/static.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.views.generic import TemplateView 5 | 6 | 7 | class About(TemplateView): 8 | template_name = 'hmeter_frontend/about.html' 9 | 10 | 11 | class Contact(TemplateView): 12 | template_name = 'hmeter_frontend/contact.html' 13 | 14 | 15 | class MigrationSchedule(TemplateView): 16 | template_name = 'hmeter_frontend/migration-schedule.html' 17 | -------------------------------------------------------------------------------- /wsgi/healthmeter/importerutils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/importerutils/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/importerutils/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | class DuplicateImporter(Exception): 5 | """ 6 | Exception raised when a duplicate importer is registered. 7 | 8 | Data members: 9 | - cls: importer base class 10 | - backend_name: name of duplicate backend 11 | """ 12 | def __init__(self, cls, backend_name): 13 | super().__init__(backend_name) 14 | self.cls = cls 15 | self.backend_name = backend_name 16 | 17 | 18 | class UnknownImporter(Exception): 19 | """ 20 | Exception raised when an importer is attempted to be retrieved for an 21 | unknown backend_name. 22 | 23 | Data members: 24 | - cls: importer base class 25 | - backend_name: name of missing backend 26 | """ 27 | def __init__(self, cls, backend_name): 28 | super().__init__(backend_name) 29 | self.cls = cls 30 | self.backend_name = backend_name 31 | -------------------------------------------------------------------------------- /wsgi/healthmeter/importerutils/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/importerutils/management/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/importerutils/mptasks.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | """ 5 | Small collection of callable classes for helping out with multiprocessing 6 | """ 7 | import logging 8 | from .shortcuts import run_importer 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class ImporterTask: 14 | def __init__(self, obj): 15 | self.obj = obj 16 | 17 | def __call__(self): 18 | try: 19 | run_importer(self.obj) 20 | 21 | except: 22 | logger.error("Importer for [%s] failed", self.obj, exc_info=True) 23 | -------------------------------------------------------------------------------- /wsgi/healthmeter/importerutils/registry.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import collections 5 | import logging 6 | 7 | from django.apps import apps 8 | from django.utils.module_loading import import_module 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | ImporterEntry = collections.namedtuple('ImporterEntry', ['cls', 'key']) 15 | 16 | importers = {} 17 | importers_by_model = {} 18 | 19 | 20 | def register_importer(importercls, model, key=None): 21 | if key is None: 22 | key = '{app_label}.{model_name}'.format( 23 | app_label=model._meta.app_label, 24 | model_name=model._meta.model_name) 25 | 26 | entry = ImporterEntry(importercls, key) 27 | importers[key] = entry 28 | importers_by_model[model] = importercls 29 | 30 | 31 | def lookup_importer(model): 32 | load_importer_modules() 33 | 34 | if isinstance(model, str): 35 | model = apps.get_model(*model.split('.', 2)) 36 | 37 | try: 38 | return importers_by_model[model] 39 | except KeyError as e: 40 | for key, value in importers_by_model.items(): 41 | if key in model.__mro__: 42 | return value 43 | raise e 44 | 45 | 46 | def get_all_importers(): 47 | load_importer_modules() 48 | return importers_by_model 49 | 50 | 51 | _importers_loaded = False 52 | 53 | 54 | def load_importer_modules(): 55 | """ 56 | Import .importers for each app in INSTALLED_APPS 57 | """ 58 | global _importers_loaded 59 | 60 | if _importers_loaded: 61 | return 62 | 63 | for app_cfg in apps.get_app_configs(): 64 | module_name = '%s.%s' % (app_cfg.name, 'importers') 65 | 66 | try: 67 | import_module(module_name) 68 | except ImportError: 69 | logger.warning("Importing %s app importers %s module", 70 | app_cfg.name, module_name) 71 | 72 | _importers_loaded = True 73 | -------------------------------------------------------------------------------- /wsgi/healthmeter/importerutils/shortcuts.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .exceptions import UnknownImporter 5 | from .registry import lookup_importer 6 | 7 | 8 | def run_importer(instance): 9 | """Shortcut function to run the importer for the given instance.""" 10 | importercls = lookup_importer(type(instance)) 11 | 12 | with importercls.get_importer_for_object(instance) as importer: 13 | importer.run() 14 | -------------------------------------------------------------------------------- /wsgi/healthmeter/importerutils/signals.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import django.dispatch 5 | 6 | import_finished = django.dispatch.Signal(providing_args=['start', 'end', 7 | 'importer']) 8 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/mlinfo/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.contrib import admin 5 | from .models import * 6 | 7 | admin.site.register(Purpose) 8 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/importers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import urllib.parse 5 | from .importer import MailImporter 6 | from . import nntp, mbox, http 7 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/importers/common.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | """Common functions for mailing list importers""" 5 | import os 6 | 7 | 8 | def get_mailing_list_path(mailing_list): 9 | """Returns a path to the mailing list data directory""" 10 | path = os.path.join(os.getenv('OPENSHIFT_DATA_DIR', 11 | os.getenv('TMPDIR', '/tmp')), 12 | 'mailing-lists', str(mailing_list.pk)) 13 | 14 | if not os.path.isdir(path): 15 | os.makedirs(path) 16 | 17 | return path 18 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/importers/http.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import logging 5 | import re 6 | 7 | from perceval.backends.core.pipermail import Pipermail 8 | 9 | from .importer import MailImporter 10 | from .common import get_mailing_list_path 11 | from .mbox import MBoxImporter 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class HttpImporter(MBoxImporter): 17 | """ 18 | Mailing list HTML archives scanner. This class scans for mbox files in the 19 | given URL, downloads them, and hooks up to MBoxImporter to import posts 20 | into the database. 21 | """ 22 | url = '' 23 | username = None 24 | password = None 25 | 26 | mailing_list_path = '' 27 | 28 | patterns = [re.compile(r'\.(?:txt|mbox)(?:.(?:gz|bz2))?(?:/thread)?$')] 29 | 30 | def __init__(self, mailing_list, username=None, password=None): 31 | """ 32 | Initializes a HttpImporter instance. It will scan `url' for mbox links 33 | and download all of them to disk, passing the resulting list of local 34 | mbox paths to the underlying MBoxImporter object. 35 | 36 | @arg mailing_list MailingList instance. @see MailImporter 37 | @arg url http or https URL to scan 38 | @arg username Username to use when requesting url. Will be 39 | encoded into POST data as `username' 40 | @arg password Password to use when requesting url. Will be 41 | encoded into POST data as `password' 42 | """ 43 | super().__init__(mailing_list) 44 | self.username = username 45 | self.password = password 46 | 47 | self.mailing_list_path = get_mailing_list_path(mailing_list) 48 | 49 | self.backend = Pipermail(mailing_list.archive_url, 50 | self.mailing_list_path) 51 | 52 | MailImporter.register_importer('http', HttpImporter) 53 | MailImporter.register_importer('https', HttpImporter) 54 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/importers/mbox.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import logging 5 | 6 | from .importer import MailImporter, Message 7 | 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class MBoxImporter(MailImporter): 13 | """Mailiing list importer using mbox as input""" 14 | 15 | def get_messages(self): 16 | """ 17 | Iterate through all messages in self.mbox_file 18 | """ 19 | messages = self.backend.fetch() 20 | 21 | for message in messages: 22 | data = message['data'] 23 | 24 | try: 25 | msg = Message(data['From'], data['Date'], data['Subject'], 26 | data['Message-ID'], data['References']) 27 | except: 28 | logger.warning('Malformed message found, skipping:\n%s', message['updated_on'], 29 | exc_info=True) 30 | msg = None 31 | 32 | # yield outside the try block to avoid capturing exceptions 33 | # that should terminate the loop instead 34 | if msg is not None: 35 | yield msg 36 | 37 | 38 | MailImporter.register_importer('mbox', MBoxImporter) 39 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/mlinfo/management/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/mlinfo/management/commands/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/management/commands/update_mailing_lists.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from healthmeter.mlinfo.importers import MailImporter 5 | from healthmeter.mlinfo.models import MailingList 6 | 7 | from healthmeter.importerutils.management.base import ImporterCommand 8 | 9 | 10 | class Command(ImporterCommand): 11 | importer = MailImporter 12 | model = MailingList 13 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/mlinfo/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.db import models 5 | from mptt.models import TreeForeignKey, TreeManyToManyField 6 | 7 | from healthmeter.managers import get_natural_key_manager 8 | from healthmeter.participantinfo.models import Participant 9 | from healthmeter.projectinfo.models import Project 10 | from healthmeter.projectinfo.decorators import resource 11 | 12 | 13 | class Purpose(models.Model): 14 | name = models.CharField(max_length=50, unique=True) 15 | objects = get_natural_key_manager('name') 16 | 17 | def __str__(self): 18 | return self.name 19 | 20 | 21 | @resource 22 | class MailingList(models.Model): 23 | posting_address = models.EmailField(unique=True) 24 | archive_url = models.CharField(max_length=10000) 25 | last_updated = models.DateTimeField(blank=True, null=True, default=None) 26 | 27 | projects = TreeManyToManyField(Project, through='MailingListProject', 28 | related_name='mailing_lists', blank=True) 29 | 30 | objects = get_natural_key_manager('posting_address') 31 | 32 | def __str__(self): 33 | return self.posting_address 34 | 35 | 36 | class MailingListProject(models.Model): 37 | mailing_list = models.ForeignKey(MailingList) 38 | project = TreeForeignKey(Project) 39 | 40 | purpose = models.ForeignKey(Purpose) 41 | 42 | objects = get_natural_key_manager('project', 'mailing_list') 43 | 44 | def __str__(self): 45 | return "%s (%s): %s" % (self.project.name, self.purpose.name, 46 | self.mailing_list.posting_address) 47 | 48 | class Meta: 49 | verbose_name = "Mailing List-Project relationship" 50 | unique_together = ('project', 'mailing_list') 51 | ordering = ('project__name',) 52 | 53 | 54 | class Post(models.Model): 55 | author = models.ForeignKey(Participant, related_name='mailing_list_posts') 56 | timestamp = models.DateTimeField(db_index=True) 57 | subject = models.TextField() 58 | message_id = models.CharField(max_length=255, unique=True) 59 | 60 | mailing_lists = models.ManyToManyField(MailingList, related_name='posts') 61 | references = models.ManyToManyField('self', related_name='replies') 62 | 63 | objects = get_natural_key_manager('message_id') 64 | 65 | def __str__(self): 66 | return self.message_id 67 | -------------------------------------------------------------------------------- /wsgi/healthmeter/mlinfo/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | # Create your views here. 5 | -------------------------------------------------------------------------------- /wsgi/healthmeter/participantinfo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/participantinfo/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/participantinfo/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .models import * 5 | from django.contrib import admin 6 | 7 | for model in (Participant, EmailAddress, EmailDomain): 8 | admin.site.register(model) 9 | -------------------------------------------------------------------------------- /wsgi/healthmeter/participantinfo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/participantinfo/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/participantinfo/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | # Create your views here. 5 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/projectinfo/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.contrib import admin 5 | from mptt.admin import MPTTModelAdmin 6 | from .models import * 7 | 8 | admin.site.register(Product) 9 | admin.site.register(Release) 10 | admin.site.register(BusinessUnit) 11 | 12 | 13 | class LicenseAdmin(admin.ModelAdmin): 14 | list_display = ('name', 'is_osi_approved') 15 | list_editable = ('is_osi_approved',) 16 | 17 | admin.site.register(License, LicenseAdmin) 18 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/decorators.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.core.exceptions import ImproperlyConfigured 5 | from itertools import chain, repeat 6 | from .models import Project 7 | from .resources import register_resource 8 | 9 | # See deprecation 10 | # https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-from-the-old-api 11 | 12 | def get_fields_with_model(cls): 13 | return [ 14 | (f, f.model if f.model != cls else None) 15 | for f in cls._meta.get_fields() 16 | if not f.is_relation 17 | or f.one_to_one 18 | or (f.many_to_one and f.related_model) 19 | ] 20 | 21 | 22 | def get_m2m_with_model(cls): 23 | return [ 24 | (f, f.model if f.model != cls else None) 25 | for f in cls._meta.get_fields() 26 | if f.many_to_many and not f.auto_created 27 | ] 28 | 29 | 30 | @classmethod 31 | def _get_project_field(cls): 32 | """ 33 | Returns (project_field_name, is_m2m) tuple 34 | """ 35 | try: 36 | return cls._project_field_cache 37 | 38 | except AttributeError: 39 | fields_iter = chain( 40 | zip(repeat(False), get_fields_with_model(cls)), 41 | zip(repeat(True), get_m2m_with_model(cls)) 42 | ) 43 | 44 | # Look for fk/1to1 field 45 | for m2m, (field, model) in fields_iter: 46 | if field.rel and field.rel.to is Project: 47 | if model is None: 48 | model = cls 49 | 50 | model._project_field_cache = (field.name, m2m) 51 | return model._project_field_cache 52 | 53 | raise ImproperlyConfigured("No foreign key pointing to Project " 54 | "on {0}.{1}".format( 55 | cls._meta.app_label, 56 | cls._meta.model_name)) 57 | 58 | 59 | def _get_projects(self): 60 | """ 61 | Gets project(s) associated with this resource. 62 | """ 63 | project_attr, is_m2m = self.get_project_field() 64 | 65 | if not is_m2m: 66 | return [getattr(self, project_attr)] 67 | 68 | else: 69 | return getattr(self, project_attr).all() 70 | 71 | 72 | def resource(model): 73 | """Decorator for resource classes""" 74 | model.get_project_field = _get_project_field 75 | model.get_projects = _get_projects 76 | 77 | register_resource(model) 78 | 79 | return model 80 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/fixtures/initial_business_units.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "fields": { 4 | "name": "Platform" 5 | }, 6 | "model": "projectinfo.businessunit", 7 | "pk": 1 8 | }, 9 | { 10 | "fields": { 11 | "name": "Cloud" 12 | }, 13 | "model": "projectinfo.businessunit", 14 | "pk": 2 15 | }, 16 | { 17 | "fields": { 18 | "name": "Middleware" 19 | }, 20 | "model": "projectinfo.businessunit", 21 | "pk": 3 22 | }, 23 | { 24 | "fields": { 25 | "name": "Storage" 26 | }, 27 | "model": "projectinfo.businessunit", 28 | "pk": 4 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/projectinfo/management/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/projectinfo/management/commands/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/management/commands/dump_last_updated.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from healthmeter.projectinfo.models import Project 6 | from healthmeter.projectinfo.resources import get_resource_models 7 | 8 | 9 | class Command(BaseCommand): 10 | args = '[....]' 11 | 12 | def handle(self, *args, **options): 13 | projects = (Project.objects.filter(id__in=args) if args else 14 | Project.objects.root_nodes()).order_by('name') 15 | res_models = list(sorted(get_resource_models(), 16 | key=lambda r: r._meta.verbose_name)) 17 | 18 | for project in projects.iterator(): 19 | print u'{0}:'.format(project.name) 20 | for res_model in res_models: 21 | project_attr, is_m2m = res_model.get_project_field() 22 | kwargs = { 23 | project_attr + '__in': project.get_descendants( 24 | include_self=True) 25 | } 26 | 27 | res_instances = list(res_model.objects.filter(**kwargs)) 28 | 29 | if res_instances: 30 | print u' - {0}:'.format( 31 | res_model._meta.verbose_name_plural) 32 | 33 | for inst in res_instances: 34 | print u' - {inst}: {last_updated}'.format( 35 | inst=inst, 36 | last_updated=getattr(inst, 'last_updated', 'Unknown') 37 | ) 38 | 39 | # empty line after each project 40 | print '' 41 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/management/commands/dump_projects.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.core.management.base import BaseCommand, CommandError 5 | from healthmeter.projectinfo.models import Project 6 | from healthmeter.projectinfo.resources import get_resource_models 7 | 8 | 9 | class Command(BaseCommand): 10 | args = '[....]' 11 | 12 | def handle(self, *args, **options): 13 | projects = (Project.objects.filter(id__in=args) if args else 14 | Project.objects.root_nodes()).order_by('name') 15 | res_models = list(sorted(get_resource_models(), 16 | key=lambda r: r._meta.verbose_name)) 17 | 18 | for project in projects.iterator(): 19 | print u'({id}) {name}:'.format(id=project.id, name=project.name) 20 | for res_model in res_models: 21 | project_attr, is_m2m = res_model.get_project_field() 22 | kwargs = { 23 | project_attr + '__in': project.get_descendants( 24 | include_self=True) 25 | } 26 | 27 | res_instances = list(res_model.objects.filter(**kwargs)) 28 | 29 | if res_instances: 30 | print u' - {0}:'.format( 31 | res_model._meta.verbose_name_plural) 32 | 33 | for inst in res_instances: 34 | print u' - ({id}) {inst}'.format( 35 | id=inst.id, 36 | inst=inst) 37 | 38 | # empty line after each project 39 | print '' 40 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/management/commands/import_releases.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import dateutil.parser 5 | from django.core.management.base import BaseCommand, CommandError 6 | import logging 7 | from optparse import make_option 8 | 9 | from healthmeter.projectinfo.models import Project 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class Command(BaseCommand): 15 | option_list = BaseCommand.option_list + ( 16 | make_option('--project', '-p', 17 | dest='project', 18 | help='Project ID to import releases for'), 19 | ) 20 | 21 | args = '[...]' 22 | 23 | help = """Import releases from s. should be a text file in the 24 | following format, where each has the format dd/mm/yyyy: 25 | 26 | 27 | """ 28 | 29 | def handle(self, *args, **options): 30 | if 'project' not in options: 31 | raise CommandError("Project ID not provided.") 32 | 33 | project = Project.objects.get(id=options['project']) 34 | 35 | logger.debug("Project is %s", project) 36 | 37 | for filename in args: 38 | logger.debug("Opening file %s", filename) 39 | 40 | with open(filename) as f: 41 | for line in f: 42 | date_str, version = line.strip().split(' ', 1) 43 | date = dateutil.parser.parse(date_str) 44 | 45 | release, created = project.releases \ 46 | .get_or_create(version=version, 47 | defaults={ 48 | 'date': date 49 | }) 50 | 51 | logger.info("%s version %s", 52 | "Imported" if created else "Found", 53 | release) 54 | 55 | if not created: 56 | release.date = date 57 | release.save() 58 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/management/commands/list_projects.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from __future__ import print_function 5 | from django.core.management.base import BaseCommand, CommandError 6 | from optparse import make_option 7 | 8 | from ...models import Project 9 | 10 | 11 | class Command(BaseCommand): 12 | option_list = BaseCommand.option_list + ( 13 | make_option('--all', '-a', 14 | dest='all', 15 | default=False, action='store_true', 16 | help=("Select all projects. If this flag isn't provided, " 17 | "then only list root projects.")), 18 | ) 19 | args = '[--all]' 20 | help = "List project IDs present in the database, one per line." 21 | 22 | def handle(self, **kwargs): 23 | getall = kwargs['all'] 24 | 25 | qs = Project.objects.all() if getall else Project.objects.root_nodes() 26 | ids = (str(pid) for pid in qs.values_list('id', flat=True).iterator()) 27 | 28 | print('\n'.join(ids)) 29 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/projectinfo/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/resources.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | resource_registry = set() 5 | 6 | 7 | def register_resource(resource_model): 8 | resource_registry.add(resource_model) 9 | 10 | 11 | def get_resource_models(): 12 | return resource_registry 13 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/tests.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import datetime 5 | from django.test import TestCase 6 | from django.db import IntegrityError 7 | 8 | from .models import * 9 | 10 | 11 | class TestProject(TestCase): 12 | """Test case that contains tests for Project model""" 13 | def test_duplicate(self): 14 | """Tests that multiple projects with the same name can exist""" 15 | p1 = Project.objects.create(name='test') 16 | p2 = Project.objects.create(name='test') 17 | 18 | self.assertNotEqual(p1, p2) 19 | 20 | def test_tree(self): 21 | """Tests MPTT tree structure of Project""" 22 | p1 = Project.objects.create(name='test') 23 | p2 = p1.children.create(name='test') 24 | 25 | self.assertEqual(p2.parent, p1) 26 | self.assertIn(p2, p1.children.all()) 27 | 28 | def test_str(self): 29 | """Tests the __unicode__ function""" 30 | p1 = Project(name='test') 31 | self.assertEqual(p1, 'test') 32 | 33 | 34 | class TestRelease(TestCase): 35 | """Test case that contains tests for Release model""" 36 | def test_unique(self): 37 | """Tests that Release is unique per version per project""" 38 | p = Project.objects.create(name='test') 39 | r = p.releases.create(version='1.0', date=datetime.date.today()) 40 | 41 | with self.assertRaises(IntegrityError): 42 | p.releases.create(version='1.0', 43 | date=(datetime.date.today() - 44 | datetime.timedelta(days=1))) 45 | 46 | def test_ordering(self): 47 | """ 48 | Tests that the ordering of project releases is by date followed by 49 | version 50 | """ 51 | p = Project.objects.create(name='test') 52 | r1 = p.releases.create(version='2.0', date=datetime.date.today()) 53 | r2 = p.releases.create(version='1.0', date=datetime.date.today()) 54 | r3 = p.releases.create(version='3.0', 55 | date=(datetime.date.today() - 56 | datetime.timedelta(days=1))) 57 | 58 | releases = p.releases.all() 59 | self.assertEqual(releases[0], r3) 60 | self.assertEqual(releases[1], r2) 61 | self.assertEqual(releases[2], r1) 62 | -------------------------------------------------------------------------------- /wsgi/healthmeter/projectinfo/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | # Create your views here. 5 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/.gitignore: -------------------------------------------------------------------------------- 1 | /local.py 2 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import os 5 | 6 | from .common import * 7 | 8 | ON_OPENSHIFT = 'OPENSHIFT_REPO_DIR' in os.environ 9 | if ON_OPENSHIFT: 10 | from .openshift import * 11 | 12 | try: 13 | from .dev import * 14 | except ImportError: 15 | pass 16 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/cardinal.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | DATABASES = { 5 | 'default': { 6 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 7 | 'NAME': 'healthmeter', 8 | 'USER': 'healthmeter', 9 | 'PASSWORD': 'healthmeter', 10 | 'HOST': 'localhost', 11 | 'PORT': '', 12 | } 13 | } 14 | 15 | CACHES = { 16 | 'default': { 17 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 18 | 'LOCATION': 'localhost:11211', 19 | 'KEY_PREFIX': 'staging' 20 | } 21 | } 22 | 23 | DEBUG = False 24 | 25 | MEDUSA_URL_PREFIX = '/staging/' 26 | STATIC_URL = '/staging/static/' 27 | MEDIA_URL = '/staging/media/' 28 | 29 | EMAIL_HOST = 'smtp.corp.redhat.com' 30 | EMAIL_PORT = 25 31 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/cardinal1x.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | DATABASES = { 5 | 'default': { 6 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 7 | 'NAME': 'healthmeter1x', 8 | 'USER': 'healthmeter', 9 | 'PASSWORD': 'healthmeter', 10 | 'HOST': 'localhost', 11 | 'PORT': '', 12 | } 13 | } 14 | 15 | CACHES = { 16 | 'default': { 17 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 18 | 'LOCATION': 'localhost:11211', 19 | 'KEY_PREFIX': 'stable1x' 20 | } 21 | } 22 | 23 | DEBUG = False 24 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/dev.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | # -*- python -*- 5 | 6 | from .djdt import * 7 | from .common import ADMINS 8 | 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 12 | 'NAME': 'healthmeter', 13 | 'USER': 'healthmeter', 14 | 'PASSWORD': 'healthmeter', 15 | 'HOST': 'localhost', 16 | 'PORT': '', 17 | } 18 | } 19 | 20 | CACHES = { 21 | 'default': { 22 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 23 | 'LOCATION': 'localhost:11211', 24 | 'KEY_PREFIX': 'healthmeterdev' 25 | } 26 | } 27 | 28 | MANAGERS = ADMINS 29 | COMPRESS_OFFLINE = False 30 | COMPRESS_ENABLED = False 31 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/djdt.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from .common import INSTALLED_APPS, MIDDLEWARE_CLASSES 5 | 6 | try: 7 | from .common import INTERNAL_IPS 8 | except ImportError: 9 | INTERNAL_IPS = () 10 | 11 | INSTALLED_APPS += ('debug_toolbar', 'cache_panel') 12 | MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) 13 | INTERNAL_IPS += ('127.0.0.1',) 14 | 15 | DEBUG_TOOLBAR_PANELS = [ 16 | 'debug_toolbar.panels.versions.VersionsPanel', 17 | 'debug_toolbar.panels.timer.TimerPanel', 18 | 'debug_toolbar.panels.settings.SettingsPanel', 19 | 'debug_toolbar.panels.headers.HeadersPanel', 20 | 'debug_toolbar.panels.request.RequestPanel', 21 | 'debug_toolbar.panels.sql.SQLPanel', 22 | 'debug_toolbar.panels.staticfiles.StaticFilesPanel', 23 | 'debug_toolbar.panels.templates.TemplatesPanel', 24 | 'debug_toolbar.panels.cache.CachePanel', 25 | 'debug_toolbar.panels.signals.SignalsPanel', 26 | 'debug_toolbar.panels.logging.LoggingPanel', 27 | 'debug_toolbar.panels.redirects.RedirectsPanel', 28 | ] 29 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/openshift.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import os 5 | 6 | DATABASES = { 7 | 'default': { 8 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 9 | 'NAME': os.environ['OPENSHIFT_APP_NAME'], 10 | 'USER': os.environ['OPENSHIFT_POSTGRESQL_DB_USERNAME'], 11 | 'PASSWORD': os.environ['OPENSHIFT_POSTGRESQL_DB_PASSWORD'], 12 | 'HOST': os.environ['OPENSHIFT_POSTGRESQL_DB_HOST'], 13 | 'PORT': os.environ['OPENSHIFT_POSTGRESQL_DB_PORT'] 14 | } 15 | } 16 | 17 | MEDIA_ROOT = os.path.join( 18 | os.environ.get('OPENSHIFT_DATA_DIR', 19 | os.path.join(os.path.dirname(os.path.abspath(__file__)), 20 | '../../')), 21 | 'media') 22 | 23 | CACHES = { 24 | 'default': { 25 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 26 | 'LOCATION': os.environ['OPENSHIFT_PYTHON_IP'] + ':22322', 27 | } 28 | } 29 | 30 | MEDUSA_MULTITHREAD = False 31 | 32 | EMAIL_HOST = 'smtp.corp.redhat.com' 33 | EMAIL_PORT = 25 34 | 35 | COMPRESS_PRECOMPILERS = ( 36 | ('text/less', 37 | os.path.join(os.environ['OPENSHIFT_DATA_DIR'], 'opt', 'less', 38 | 'node_modules/.bin/lessc') + ' {infile}'), 39 | ) 40 | -------------------------------------------------------------------------------- /wsgi/healthmeter/settings/pseudo_openshift.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | # -*- python -*- 5 | 6 | from .common import LOGGING 7 | 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 11 | 'NAME': 'healthmeter', 12 | 'USER': 'healthmeter', 13 | 'PASSWORD': 'healthmeter', 14 | 'HOST': 'localhost', 15 | 'PORT': '', 16 | } 17 | } 18 | 19 | CACHES = { 20 | 'default': { 21 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 22 | 'LOCATION': 'localhost:11211', 23 | 'KEY_PREFIX': 'healthmeterdev' 24 | } 25 | } 26 | 27 | LOGGING['handlers']['console']['level'] = 'INFO' 28 | -------------------------------------------------------------------------------- /wsgi/healthmeter/templates/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | There is no spoon^Wpage 5 | 6 | 7 | 8 |

404

9 |

10 | We're sorry, but the page you are seeking for could not be found. Please 11 | accept this little haiku instead: 12 |

13 | 14 |
{{ quote }}
15 | 16 | 17 | -------------------------------------------------------------------------------- /wsgi/healthmeter/test-metrics.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | #!/usr/bin/python 5 | 6 | import os 7 | import sys 8 | 9 | os.environ['DJANGO_SETTINGS_MODULE'] = 'healthmeter.settings' 10 | sys.path.insert(0, '/home/hyperair/src/healthmeter/wsgi') 11 | 12 | from healthmeter.hmeter_frontend.metrics import algorithms 13 | from healthmeter.hmeter_frontend.models import Project 14 | 15 | for p in Project.objects.root_nodes(): 16 | for i in algorithms.values(): 17 | print("{0}: {1}".format(i.__name__, i(p).normalized_score)) 18 | -------------------------------------------------------------------------------- /wsgi/healthmeter/test-nntp-importer.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | #!/usr/bin/python 5 | 6 | import os 7 | import sys 8 | 9 | os.environ['DJANGO_SETTINGS_MODULE'] = 'healthmeter.settings' 10 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 11 | 12 | from healthmeter.hmeter_frontend.models import MailingList 13 | from healthmeter.hmeter_frontend.importers import mailing_list 14 | 15 | ml = MailingList.objects.filter(archive_url__startswith='nntp')[0] 16 | importer = mailing_list.MailImporter.get_importer(ml.archive_url) 17 | 18 | importer_instance = importer(ml) 19 | importer_instance.run() 20 | -------------------------------------------------------------------------------- /wsgi/healthmeter/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.conf.urls import include, url, static 5 | from django.conf import settings 6 | 7 | from django.contrib import admin 8 | admin.autodiscover() 9 | 10 | import django_js_reverse.views 11 | 12 | import healthmeter.hmeter_frontend.urls 13 | from healthmeter.views.errors import PageNotFoundView 14 | 15 | urlpatterns = [ 16 | url(r'^admin/', admin.site.urls), 17 | url(r'^jsreverse/', django_js_reverse.views.urls_js, 18 | name='js_reverse'), 19 | ] 20 | 21 | if settings.DEBUG: 22 | urlpatterns += static.static(settings.MEDIA_URL, 23 | document_root=settings.MEDIA_ROOT) 24 | 25 | urlpatterns += [ 26 | url(r'', include(healthmeter.hmeter_frontend.urls, 27 | namespace="hmeter_frontend")), 28 | ] 29 | 30 | handler404 = PageNotFoundView.as_view() 31 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/vcsinfo/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "vcsinfo.type", 5 | "fields": {"name": "git"} 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/importers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | import os 5 | from .common import VcsImporter 6 | from . import git_importer 7 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/importers/dvcsimporter.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | """Abstracted algorithm for importing DVCS repositories""" 5 | 6 | import abc 7 | from django.db import transaction 8 | import logging 9 | 10 | from .common import VcsImporter 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class DVcsImporter(VcsImporter): 16 | __metaclass__ = abc.ABCMeta 17 | 18 | @abc.abstractmethod 19 | def get_commit_authorship(self, commit): 20 | """ 21 | Get the authorship information for this commit 22 | 23 | @arg commit Commit ID to lookup authorship information for 24 | @returns (author, timestamp) tuple 25 | """ 26 | pass 27 | 28 | @abc.abstractmethod 29 | def count_lines(self, commit): 30 | """ 31 | Get the SLOC for the commit. 32 | 33 | @arg commit Commit ID to count lines for 34 | @returns integer containing lines of code for the commit 35 | 36 | """ 37 | pass 38 | 39 | @transaction.atomic 40 | def _run(self): 41 | logger.info("Importing commits for repository [%s]", self.object.url) 42 | commit_stack = self.backend.fetch(latest_items=True) 43 | 44 | for current_commit in commit_stack: 45 | line_count = self.count_lines(current_commit) 46 | author, timestamp = self.get_commit_authorship(current_commit) 47 | 48 | logger.info("Importing commit [%s]...", current_commit) 49 | _, created = self.object.commits.get_or_create( 50 | commit_id=current_commit['data']['commit'], 51 | defaults={ 52 | 'author': author, 53 | 'timestamp': timestamp, 54 | 'line_count': line_count 55 | }) 56 | 57 | if created: 58 | self.record_timestamp(timestamp) 59 | else: 60 | logger.info("[%s] already in database. Skipping.", 61 | current_commit) 62 | continue 63 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/importers/git_importer.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | """This module contains the git importer for the healthmeter project""" 5 | 6 | import logging 7 | import re 8 | 9 | import grimoirelab.toolkit.datetime 10 | import perceval.backends.core.git as git 11 | 12 | from healthmeter.hmeter_frontend.models import * 13 | 14 | from .common import VcsImporter 15 | from .dvcsimporter import DVcsImporter 16 | 17 | logger = logging.getLogger(__name__) # pylint: disable-msg=C0103 18 | 19 | 20 | class GitImporter(DVcsImporter): 21 | def __init__(self, dj_repo): 22 | super().__init__(dj_repo) 23 | self.backend = None 24 | self._init_repository() 25 | self.cached_line_count = {} 26 | 27 | def _init_repository(self): 28 | logger.info("Initializing importer for repository [%s]", 29 | self.object.url) 30 | self.repo_path = self._get_checkout_path() 31 | 32 | self.backend = git.Git(self.object.url, self.repo_path) 33 | 34 | userid_ext_hggit_regex = re.compile(r' ?ext:\(.*\) ?') 35 | 36 | def get_commit_authorship(self, commit): 37 | data = commit['data'] 38 | 39 | # Sanitize ext:(....) blocks from GIT_AUTHOR_NAME. hg-git adds these 40 | # for metadata. 41 | userid = self.userid_ext_hggit_regex.sub(' ', data['Author']) 42 | 43 | author = self.get_committer(userid=userid) 44 | timestamp = grimoirelab.toolkit.datetime.str_to_datetime(data['CommitDate']) 45 | 46 | return (author, timestamp) 47 | 48 | def count_lines(self, commit): 49 | # TODO: not implemented yet 50 | return 0 51 | 52 | VcsImporter.register_importer('git', GitImporter) 53 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/vcsinfo/management/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/vcsinfo/management/commands/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/management/commands/update_vcs_repositories.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from healthmeter.importerutils.management.base import ImporterCommand 5 | 6 | from healthmeter.vcsinfo.importers import VcsImporter 7 | from healthmeter.vcsinfo.models import Repository 8 | 9 | 10 | class Command(ImporterCommand): 11 | importer = VcsImporter 12 | model = Repository 13 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/vcsinfo/migrations/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/models.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.db import models 5 | from mptt.models import TreeForeignKey 6 | 7 | from healthmeter.participantinfo.models import Participant 8 | from healthmeter.projectinfo.models import Project 9 | from healthmeter.projectinfo.decorators import resource 10 | 11 | 12 | class Type(models.Model): 13 | name = models.CharField(max_length=255, unique=True) 14 | 15 | class Meta: 16 | verbose_name_plural = "VCS types" 17 | verbose_name = "VCS type" 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | 23 | @resource 24 | class Repository(models.Model): 25 | type = models.ForeignKey(Type, related_name='repositories') 26 | project = TreeForeignKey(Project, related_name='repositories') 27 | url = models.CharField(max_length=10000, unique=True) 28 | last_updated = models.DateTimeField(blank=True, null=True, default=None) 29 | 30 | class Meta: 31 | verbose_name_plural = "VCS repositories" 32 | verbose_name = "VCS repository" 33 | 34 | def __stre__(self): 35 | return self.url 36 | 37 | 38 | class Committer(models.Model): 39 | participant = models.ForeignKey(Participant, related_name='committer_ids') 40 | repository = models.ForeignKey(Repository, related_name='committers') 41 | userid = models.CharField(max_length=200) 42 | 43 | class Meta: 44 | unique_together = ('userid', 'repository') 45 | 46 | def __str__(self): 47 | return self.userid 48 | 49 | 50 | class Commit(models.Model): 51 | repository = models.ForeignKey(Repository, related_name='commits') 52 | commit_id = models.CharField(max_length=255) 53 | author = models.ForeignKey(Committer, related_name='commits') 54 | timestamp = models.DateTimeField(db_index=True) 55 | 56 | line_count = models.PositiveIntegerField(null=False, default=0) 57 | 58 | class Meta: 59 | verbose_name = "VCS commit" 60 | verbose_name_plural = "VCS commits" 61 | 62 | unique_together = ('repository', 'commit_id') 63 | 64 | def __str__(self): 65 | return '{0} on {1}'.format(self.commit_id, self.repository) 66 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/tests.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | """ 5 | Tests for the vcsinfo app 6 | """ 7 | import datetime 8 | 9 | from django.db import IntegrityError 10 | from django.test import TestCase 11 | 12 | from .models import * 13 | 14 | 15 | class TestType(TestCase): 16 | def test_unique(self): 17 | Type.objects.get(name='git') 18 | 19 | with self.assertRaises(IntegrityError): 20 | Type.objects.create(name='git') 21 | 22 | def test_str(self): 23 | t = Type.objects.get(name='git') 24 | self.assertEqual(t, 'git') 25 | 26 | 27 | class TestRepository(TestCase): 28 | def test_duplicate(self): 29 | t = Type.objects.get(name='git') 30 | url = 'git://foo/bar.git' 31 | 32 | Repository.objects.create(type=t, url=url) 33 | 34 | with self.assertRaises(IntegrityError): 35 | Repository.objects.create(type=t, url=url) 36 | 37 | def test_str(self): 38 | t = Type.objects.get(name='git') 39 | url = 'git://foob/bar.git' 40 | r = Repository.objects.create(type=t, url=url) 41 | 42 | self.assertEqual(r, url) 43 | 44 | 45 | class TestCommit(TestCase): 46 | def test_duplicate(self): 47 | t = Type.objects.get(name='git') 48 | url = 'git://foo/bar.git' 49 | p = Participant.objects.create(name='test') 50 | time = datetime.datetime.utcnow() 51 | 52 | r = Repository.objects.create(type=t, url=url) 53 | 54 | committer = Committer.objects.create(participant=p, repository=r, 55 | userid=p.name) 56 | 57 | c = r.commits.create(commit_id='1234', author=committer, 58 | timestamp=time) 59 | self.assertEqual(c.repository, r) 60 | self.assertIn(c, r.commits.all()) 61 | 62 | with self.assertRaises(IntegrityError): 63 | r.commits.create(commit_id='1234', author=committer, 64 | timestamp=time) 65 | -------------------------------------------------------------------------------- /wsgi/healthmeter/vcsinfo/views.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | # Create your views here. 5 | -------------------------------------------------------------------------------- /wsgi/healthmeter/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaoss/prospector/772165bfde3f1c5e4b8fe2e7c4b78046edc7e8d6/wsgi/healthmeter/views/__init__.py -------------------------------------------------------------------------------- /wsgi/healthmeter/views/generic.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Red Hat, Inc. 2 | # License: GPLv3 or any later version 3 | 4 | from django.http import HttpResponse 5 | from django.views.generic import View 6 | import json 7 | 8 | 9 | class RawDataView(View): 10 | """ 11 | Generic Django view for outputting raw data 12 | """ 13 | output = '' 14 | content_type = 'text/plain' 15 | 16 | def get_output(self): 17 | return self.output 18 | 19 | def get(self, request, *args, **kwargs): 20 | return HttpResponse(self.get_output(), content_type=self.content_type) 21 | 22 | 23 | class JsonView(RawDataView): 24 | """ 25 | Generic Django view for outputting stuff via Json 26 | """ 27 | data = {} 28 | content_type = 'application/json' 29 | 30 | def get_data(self): 31 | return self.data 32 | 33 | def get_output(self): 34 | return json.dumps(self.get_data()) 35 | -------------------------------------------------------------------------------- /wsgi/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2017 Red Hat, Inc. 3 | # License: GPLv3 or any later version 4 | 5 | import os 6 | import sys 7 | 8 | if __name__ == "__main__": 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "healthmeter.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError: 13 | # The above import may fail for some other reason. Ensure that the 14 | # issue is really that Django is missing to avoid masking other 15 | # exceptions on Python 2. 16 | try: 17 | import django 18 | except ImportError: 19 | raise ImportError( 20 | "Couldn't import Django. Are you sure it's installed and " 21 | "available on your PYTHONPATH environment variable? Did you " 22 | "forget to activate a virtual environment?" 23 | ) 24 | raise 25 | execute_from_command_line(sys.argv) 26 | -------------------------------------------------------------------------------- /wsgi/static/README: -------------------------------------------------------------------------------- 1 | Public, static content goes here. Users can create rewrite rules to link to 2 | content in the static dir. For example, django commonly uses /media/ 3 | directories for static content. For example in a .htaccess file in a 4 | wsgi/.htaccess location, developers could put: 5 | 6 | RewriteEngine On 7 | RewriteRule ^application/media/(.+)$ /static/media/$1 [L] 8 | 9 | Then copy the media/* content to yourapp/wsgi/static/media/ and it should 10 | just work. 11 | 12 | Note: The ^application/ part of the URI match is required. 13 | --------------------------------------------------------------------------------