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