Include last built page for all devices in the last 3mo
[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('Devices', '.web_devices'),
30 View('Stats', '.web_stats'),
31 ))
32
33 headers = {'Private-Token': os.environ.get('GITLAB_TOKEN', '')}
34
35 def version():
36 return os.environ.get("VERSION", "dev")[:6]
37
38 app.jinja_env.globals.update(version=version)
39
40 def parse_args():
41 args = {}
42 if request.args:
43 if 'status' in request.args:
44 args['build_status'] = request.args.get('status')
45 if 'device' in request.args:
46 args['build_device'] = request.args.get('device')
47 if 'version' in request.args:
48 args['build_version'] = request.args.get('version')
49 if 'type' in request.args:
50 args['build_type'] = request.args.get('type')
51 if 'date' in request.args:
52 date = datetime.datetime.strptime(request.args.get('date'), '%Y-%m-%d').date()
53 args['build_date'] = datetime.datetime.strptime(request.args.get('date'), '%Y-%m-%d').date()
54 if 'id' in request.args:
55 args['build_id'] = request.args.get('id')
56 return args
57
58 @cache.memoize()
59 def stats():
60
61 runner_build_times = models.Build.query.join(models.Build.build_runner).with_entities(
62 models.Runner.runner_name,
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, models.Runner.runner_name).all()
69
70 all_build_times = models.Build.query.with_entities(
71 models.Build.build_version,
72 func.avg(models.Build.build_duration),
73 func.max(models.Build.build_duration),
74 func.min(models.Build.build_duration),
75 func.sum(models.Build.build_duration)
76 ).group_by(models.Build.build_version).all()
77
78 runner_build_status = models.Build.query.join(models.Build.build_runner).with_entities(
79 models.Runner.runner_name,
80 models.Build.build_status,
81 func.count(models.Build.build_status)
82 ).group_by(models.Runner.runner_name, models.Build.build_status).all()
83
84
85 stats = {
86 'builds': {
87 'all': {}
88 },
89 'times': {
90 'all': {}
91 }
92 }
93
94 for build_time in all_build_times:
95 stats['times']['all'][build_time[0]] = {
96 'avg': build_time[1] if build_time[1] else 0,
97 'max': build_time[2] if build_time[2] else 0,
98 'min': build_time[3] if build_time[3] else 0,
99 'sum': build_time[4] if build_time[4] else 0,
100 }
101
102 for build_time in runner_build_times:
103 stats['times'].setdefault(build_time[0], {})[build_time[1]] = {
104 'avg': build_time[2] if build_time[2] else 0,
105 'max': build_time[3] if build_time[3] else 0,
106 'min': build_time[4] if build_time[4] else 0,
107 'sum': build_time[5] if build_time[5] else 0,
108 }
109
110 for build_status in runner_build_status:
111 stats['builds']['all'].setdefault(build_status[1], 0)
112 stats['builds']['all'][build_status[1]] += build_status[2]
113
114 stats['builds'].setdefault(build_status[0], {})[build_status[1]] = build_status[2]
115 return stats
116
117 @app.route('/')
118 def web_index():
119 try:
120 args = parse_args()
121 except ValueError:
122 return "Invalid Date", 400
123 builds = models.Build.paginate(args)
124 return render_template('builds.html', builds=builds)
125
126 @app.route('/runners/<string:runner>')
127 def web_runner(runner):
128 try:
129 args = parse_args()
130 except ValueError:
131 return "Invalid Date", 400
132 runner = models.Runner.get({'runner_name': runner}).first()
133 args['build_runner'] = runner
134 builds = models.Build.paginate(args)
135 return render_template('runner.html', runner=runner, builds=builds)
136
137 @app.route('/stats')
138 def web_stats():
139 stats_ = stats()
140 runners = ['all'] + [x for x in sorted(stats_['builds'].keys()) if x != 'all']
141 return render_template('stats.html', stats=stats_, runners=runners)
142
143 @app.route("/runners/")
144 def web_runners():
145 #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;
146 subquery = models.Build.query.filter(models.Build.build_status == "success").order_by(models.Build.build_date).subquery()
147 runners = models.Runner.query.outerjoin(
148 subquery, subquery.c.build_runner_id == models.Runner.runner_id
149 ).with_entities(
150 models.Runner.runner_id,
151 models.Runner.runner_name,
152 models.Runner.runner_sponsor,
153 models.Runner.runner_sponsor_url,
154 subquery.c.build_date
155 ).group_by(models.Runner.runner_id).order_by(subquery.c.build_date.desc(), models.Runner.runner_name).all()
156 return render_template('runners.html', runners=runners)
157
158 @app.route("/devices/")
159 def web_devices():
160 builds = models.Build.query.filter(models.Build.build_date > datetime.date.today() - datetime.timedelta(90)).group_by(models.Build.build_device).having(func.max(models.Build.build_date)).order_by(func.lower(models.Build.build_device)).all()
161 return render_template("devices.html", builds=builds)
162
163 @app.route('/api/v1/builds')
164 def api_builds():
165 try:
166 args = parse_args()
167 except ValueError:
168 return jsonify({'error': 'Invalid Date'}), 400
169 builds = models.Build.paginate(args).items
170 if not builds:
171 abort(404)
172 return jsonify([x.as_dict() for x in builds])
173
174 @app.route('/api/v1/runners')
175 def api_runners():
176 runners = models.Runner.get().all()
177 if not runners:
178 abort(404)
179 return jsonify([x.as_dict() for x in runners])
180
181 @app.route('/api/v1/runners/<string:runner>')
182 def api_runner(runner):
183 try:
184 args = parse_args()
185 except ValueError:
186 return jsonify({"Invalid Date"}), 400
187 runner = models.Runner.get({"runner_name": runner}).first()
188 if not runner:
189 abort(404)
190 return jsonify(runner.as_dict())
191
192 @app.route('/api/v1/stats')
193 def api_stats():
194 return jsonify(stats())
195
196 @app.route('/stats')
197 @app.route("/webhook", methods=('POST',))
198 def process_webhook():
199 gitlab.webhooks.process(request)
200 return "OK", 200
201
202 if __name__ == '__main__':
203 app.run(host='0.0.0.0', port=5000)