Commit | Line | Data |
---|---|---|
8fca1ec2 | 1 | import datetime |
31329c3f TP |
2 | import os |
3 | ||
4 | import requests | |
9421395c | 5 | from flask import Flask, render_template, request, abort, jsonify |
8fca1ec2 | 6 | from flask_bootstrap import Bootstrap |
31329c3f TP |
7 | from flask_caching import Cache |
8 | from flask_migrate import Migrate | |
8fca1ec2 TP |
9 | from flask_nav import Nav |
10 | from flask_nav.elements import Navbar, Text, View | |
31329c3f TP |
11 | from flask_sqlalchemy import SQLAlchemy |
12 | ||
9421395c TP |
13 | from sqlalchemy import orm, func |
14 | ||
31329c3f TP |
15 | from ui import gitlab, config, models |
16 | ||
17 | app = Flask(__name__) | |
8fca1ec2 | 18 | Bootstrap(app) |
31329c3f TP |
19 | app.config.from_object(config) |
20 | cache = Cache(app) | |
21 | models.db.init_app(app) | |
22 | migrate = Migrate(app, models.db) | |
8fca1ec2 | 23 | nav = Nav(app) |
31329c3f | 24 | |
8fca1ec2 TP |
25 | nav.register_element('top', Navbar( |
26 | "LineageOS Builds", | |
9421395c TP |
27 | View('Builds', '.web_index'), |
28 | View('Runners', '.web_runners'), | |
29 | View('Stats', '.web_stats') | |
8fca1ec2 | 30 | )) |
31329c3f TP |
31 | |
32 | headers = {'Private-Token': os.environ.get('GITLAB_TOKEN', '')} | |
33 | ||
60410d60 | 34 | def parse_args(): |
8fca1ec2 TP |
35 | args = {} |
36 | if request.args: | |
37 | if 'status' in request.args: | |
38 | args['build_status'] = request.args.get('status') | |
39 | if 'device' in request.args: | |
40 | args['build_device'] = request.args.get('device') | |
41 | if 'version' in request.args: | |
42 | args['build_version'] = request.args.get('version') | |
43 | if 'type' in request.args: | |
44 | args['build_type'] = request.args.get('type') | |
45 | if 'date' in request.args: | |
60410d60 TP |
46 | date = datetime.datetime.strptime(request.args.get('date'), '%Y-%m-%d').date() |
47 | args['build_date'] = datetime.datetime.strptime(request.args.get('date'), '%Y-%m-%d').date() | |
48 | return args | |
49 | ||
616663f4 | 50 | @cache.memoize() |
9421395c TP |
51 | def stats(): |
52 | ||
53 | runner_build_times = models.Build.query.join(models.Build.build_runner).with_entities( | |
54 | models.Runner.runner_name, | |
55 | models.Build.build_version, | |
56 | func.avg(models.Build.build_duration), | |
57 | func.max(models.Build.build_duration), | |
58 | func.min(models.Build.build_duration), | |
59 | func.sum(models.Build.build_duration) | |
60 | ).group_by(models.Build.build_version, models.Runner.runner_name).all() | |
61 | ||
62 | all_build_times = models.Build.query.with_entities( | |
63 | models.Build.build_version, | |
64 | func.avg(models.Build.build_duration), | |
65 | func.max(models.Build.build_duration), | |
66 | func.min(models.Build.build_duration), | |
67 | func.sum(models.Build.build_duration) | |
68 | ).group_by(models.Build.build_version).all() | |
69 | ||
70 | runner_build_status = models.Build.query.join(models.Build.build_runner).with_entities( | |
71 | models.Runner.runner_name, | |
72 | models.Build.build_status, | |
73 | func.count(models.Build.build_status) | |
74 | ).group_by(models.Runner.runner_name, models.Build.build_status).all() | |
75 | ||
76 | ||
77 | stats = { | |
78 | 'builds': { | |
79 | 'all': {} | |
80 | }, | |
81 | 'times': { | |
82 | 'all': {} | |
83 | } | |
84 | } | |
85 | ||
86 | for build_time in all_build_times: | |
87 | stats['times']['all'][build_time[0]] = { | |
88 | 'avg': build_time[1] if build_time[1] else 0, | |
89 | 'max': build_time[2] if build_time[2] else 0, | |
90 | 'min': build_time[3] if build_time[3] else 0, | |
91 | 'sum': build_time[4] if build_time[4] else 0, | |
92 | } | |
93 | ||
94 | for build_time in runner_build_times: | |
95 | stats['times'].setdefault(build_time[0], {})[build_time[1]] = { | |
96 | 'avg': build_time[2] if build_time[2] else 0, | |
97 | 'max': build_time[3] if build_time[3] else 0, | |
98 | 'min': build_time[4] if build_time[4] else 0, | |
99 | 'sum': build_time[5] if build_time[5] else 0, | |
100 | } | |
101 | ||
102 | for build_status in runner_build_status: | |
103 | stats['builds']['all'].setdefault(build_status[1], 0) | |
104 | stats['builds']['all'][build_status[1]] += build_status[2] | |
105 | ||
106 | stats['builds'].setdefault(build_status[0], {})[build_status[1]] = build_status[2] | |
107 | return stats | |
108 | ||
60410d60 | 109 | @app.route('/') |
9421395c | 110 | def web_index(): |
60410d60 TP |
111 | try: |
112 | args = parse_args() | |
113 | except ValueError: | |
114 | return "Invalid Date", 400 | |
9421395c | 115 | builds = models.Build.paginate(args) |
8fca1ec2 TP |
116 | return render_template('builds.html', builds=builds) |
117 | ||
118 | @app.route('/runners/<string:runner>') | |
9421395c | 119 | def web_runner(runner): |
60410d60 TP |
120 | try: |
121 | args = parse_args() | |
122 | except ValueError: | |
123 | return "Invalid Date", 400 | |
9421395c | 124 | runner = models.Runner.get({'runner_name': runner}).first() |
60410d60 | 125 | args['build_runner'] = runner |
9421395c | 126 | builds = models.Build.paginate(args) |
8fca1ec2 TP |
127 | return render_template('runner.html', runner=runner, builds=builds) |
128 | ||
9421395c TP |
129 | @app.route('/stats') |
130 | def web_stats(): | |
028304dd TP |
131 | stats_ = stats() |
132 | runners = ['all'] + [x for x in sorted(stats_['builds'].keys()) if x != 'all'] | |
133 | return render_template('stats.html', stats=stats_, runners=runners) | |
9421395c | 134 | |
8fca1ec2 | 135 | @app.route("/runners/") |
9421395c TP |
136 | def web_runners(): |
137 | runners = models.Runner.get().all() | |
8fca1ec2 | 138 | return render_template('runners.html', runners=runners) |
31329c3f | 139 | |
9421395c TP |
140 | @app.route('/api/v1/builds') |
141 | def api_builds(): | |
142 | try: | |
143 | args = parse_args() | |
144 | except ValueError: | |
145 | return jsonify({'error': 'Invalid Date'}), 400 | |
146 | builds = models.Build.paginate(args).items | |
147 | if not builds: | |
148 | abort(404) | |
149 | return jsonify([x.as_dict() for x in builds]) | |
150 | ||
151 | @app.route('/api/v1/runners') | |
152 | def api_runners(): | |
153 | runners = models.Runner.get().all() | |
154 | if not runners: | |
155 | abort(404) | |
156 | return jsonify([x.as_dict() for x in runners]) | |
157 | ||
158 | @app.route('/api/v1/runners/<string:runner>') | |
159 | def api_runner(runner): | |
160 | try: | |
161 | args = parse_args() | |
162 | except ValueError: | |
163 | return jsonify({"Invalid Date"}), 400 | |
164 | runner = models.Runner.get({"runner_name": runner}).first() | |
165 | if not runner: | |
166 | abort(404) | |
167 | return jsonify(runner.as_dict()) | |
168 | ||
169 | @app.route('/api/v1/stats') | |
170 | def api_stats(): | |
171 | return jsonify(stats()) | |
172 | ||
173 | @app.route('/stats') | |
31329c3f TP |
174 | @app.route("/webhook", methods=('POST',)) |
175 | def process_webhook(): | |
176 | gitlab.webhooks.process(request) | |
177 | return "OK", 200 | |
178 | ||
179 | if __name__ == '__main__': | |
180 | app.run(host='0.0.0.0', port=5000) |