diff --git a/.moban.yaml b/.moban.yaml index 5bf25cb1..f9db2b3c 100644 --- a/.moban.yaml +++ b/.moban.yaml @@ -9,7 +9,7 @@ packages: - gci - gsoc - gamification - - log + - ci_build - meta_review - model - unassigned_issues diff --git a/.nocover.yaml b/.nocover.yaml index b2207eb5..4757eb64 100644 --- a/.nocover.yaml +++ b/.nocover.yaml @@ -8,7 +8,7 @@ nocover_file_globs: - community/git.py - gci/*.py - gsoc/*.py - - log/*.py + - ci_build/*.py - meta_review/handler.py - model/*.py - openhub/*.py diff --git a/ci_build/view_log.py b/ci_build/view_log.py new file mode 100644 index 00000000..8c476ae3 --- /dev/null +++ b/ci_build/view_log.py @@ -0,0 +1,131 @@ +import re +import json +import os +import sys + +from django.views.generic import TemplateView + +from community.views import get_header_and_footer +from community.git import ( + get_org_name, + get_owner, + get_deploy_url, + get_upstream_deploy_url +) + + +class BuildLogsView(TemplateView): + template_name = 'build_logs.html' + + def copy_build_logs_json(self, ci_build_jsons): + """ + :param ci_build_jsons: A dict of directories path + :return: A boolean, whether the build file is copied + """ + if os.path.isfile(ci_build_jsons['public_path']): + if sys.platform == 'linux': + os.popen('cp {} {}'.format( + ci_build_jsons['site_path'], + ci_build_jsons['public_path'])) + os.popen('cp {} {}'.format( + ci_build_jsons['site_path'], + ci_build_jsons['static_path'])) + else: + os.popen('copy {} {}'.format( + ci_build_jsons['site_path'], + ci_build_jsons['public_path'])) + os.popen('copy {} {}'.format( + ci_build_jsons['site_path'], + ci_build_jsons['static_path'])) + return True + return False + + def create_and_copy_build_logs_json(self, logs, level_specific_logs): + """ + Create a build logs detailed json file in ./_site directory and copy + that file in the ./static and ./public/static directories + :param logs: A list of all lines in build log file + :param level_specific_logs: A dict containing logs divided in their + respective categories + :return: A boolean, whether the files were copied or not + """ + ci_build_jsons = { + 'site_path': './_site/ci-build-detailed-logs.json', + 'public_path': './public/static/ci-build-detailed-logs.json', + 'static_path': './static/ci-build-detailed-logs.json' + } + with open(ci_build_jsons['site_path'], 'w+') as build_logs_file: + data = { + 'logs': logs, + 'logs_level_Specific': level_specific_logs + } + json.dump(data, build_logs_file, indent=4) + return self.copy_build_logs_json(ci_build_jsons) + + def get_build_logs(self, log_file_path): + """ + :param log_file_path: build logs file path + :return: a tuple of two where the first element in tuple refers to + a list of build logs in the file, and the second element is a dict + which categorizes the build logs into 5 categories - INFO, DEBUG, + WARNING, ERROR nad CRITICAL + """ + log_lines = [] + log_level_specific_lines = { + 'INFO': [], + 'DEBUG': [], + 'WARNING': [], + 'ERROR': [], + 'CRITICAL': [] + } + with open(log_file_path) as log_file: + previous_found_level = None + for line in log_file: + log_lines.append(line) + levels = re.findall(r'\[[A-Z]+]', line) + if levels: + level = levels[0] + level = previous_found_level = level[1:-1] + log_level_specific_lines[level].append(line) + elif previous_found_level: + log_level_specific_lines[previous_found_level].append( + line) + return log_lines, log_level_specific_lines + + def check_build_logs_stored(self): + """ + Check whether the build logs json file is copied to _site and public + directories or not + :return: A Boolean + """ + log_file_path = './_site/community.log' + log_file_exists = os.path.isfile(log_file_path) + if log_file_exists: + logs, level_specific_logs = self.get_build_logs(log_file_path) + return self.create_and_copy_build_logs_json(logs, + level_specific_logs) + return False + + def get_build_info(self): + """ + Get the information about build, like who deployed the website i.e. + owner, name of the organization or user etc. + :return: A dict having information about build related details + """ + data = { + 'Org name': get_org_name(), + 'Owner': get_owner(), + 'Deploy URL': get_deploy_url(), + } + try: + data['Upstream deploy URL'] = get_upstream_deploy_url() + except RuntimeError: + data['Upstream deploy URL'] = 'Not found' + return data + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context = get_header_and_footer(context) + context['build_info'] = self.get_build_info() + context['logs_stored'] = self.check_build_logs_stored() + return context diff --git a/community/urls.py b/community/urls.py index cb37e8e2..a02a1971 100644 --- a/community/urls.py +++ b/community/urls.py @@ -6,10 +6,10 @@ from django.conf.urls.static import static from django.conf import settings -from community.views import HomePageView, info +from community.views import HomePageView from gci.views import index as gci_index from gci.feeds import LatestTasksFeed as gci_tasks_rss -from log.view_log import index as log_index +from ci_build.view_log import BuildLogsView from data.views import index as contributors_index from gamification.views import index as gamification_index from meta_review.views import index as meta_review_index @@ -78,12 +78,6 @@ def get_organization(): distill_func=get_index, distill_file='index.html', ), - distill_url( - 'info.txt', info, - name='index', - distill_func=get_index, - distill_file='info.txt', - ), distill_url( r'gci/tasks/rss.xml', gci_tasks_rss(), name='gci-tasks-rss', @@ -97,10 +91,10 @@ def get_organization(): distill_file='gci/index.html', ), distill_url( - r'log/', log_index, - name='log', + r'ci/build/', BuildLogsView.as_view(), + name='ci_build', distill_func=get_index, - distill_file='log/index.html', + distill_file='ci/build/index.html', ), distill_url( r'contributors/$', contributors_index, diff --git a/community/views.py b/community/views.py index 5c1bbb2f..38c83d23 100644 --- a/community/views.py +++ b/community/views.py @@ -4,14 +4,10 @@ from trav import Travis -from django.http import HttpResponse from django.views.generic.base import TemplateView from .git import ( - get_deploy_url, get_org_name, - get_owner, - get_upstream_deploy_url, get_remote_url ) from data.models import Team @@ -114,20 +110,3 @@ def get_context_data(self, **kwargs): context['top_gamification_users'] = self.get_top_gamification_users( count=5) return context - - -def info(request): - data = { - 'Org name': get_org_name(), - 'Owner': get_owner(), - 'Deploy URL': get_deploy_url(), - } - try: - upstream_deploy_url = get_upstream_deploy_url() - data['Upstream deploy URL'] = upstream_deploy_url - except RuntimeError: - data['Upstream deploy URL'] = 'Not found' - - s = '\n'.join(name + ': ' + value - for name, value in data.items()) - return HttpResponse(s) diff --git a/log/view_log.py b/log/view_log.py deleted file mode 100644 index facc4190..00000000 --- a/log/view_log.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.http import HttpResponse - - -def index(request): - logs = get_logs() - return HttpResponse('
'.join(logs)) - - -def get_logs(): - with open('./_site/community.log') as log_file: - for line in log_file: - yield line.rstrip() diff --git a/setup.cfg b/setup.cfg index a3f699bf..60da3717 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,7 @@ testpaths = gci gsoc gamification - log + ci_build meta_review model unassigned_issues @@ -69,7 +69,7 @@ source = gci gsoc gamification - log + ci_build meta_review model unassigned_issues @@ -80,7 +80,7 @@ omit = community/git.py gci/*.py gsoc/*.py - log/*.py + ci_build/*.py meta_review/handler.py model/*.py openhub/*.py diff --git a/static/css/build_logs.css b/static/css/build_logs.css new file mode 100644 index 00000000..44922333 --- /dev/null +++ b/static/css/build_logs.css @@ -0,0 +1,84 @@ +.build-info-section section, +.build-logs-section section { + min-width: 300px; + width: 80%; +} + +.build-information, +.build-logs { + background-color: black; + padding-left: 10px; + font-weight: bold; + color: white; +} + +.build-information p { + font-size: 1.5em; + margin: 0; +} + +.build-logs { + max-height: 900px; + overflow: scroll; + overflow-x: hidden; + overflow-y: auto; +} + +.build-logs p { + margin: 0; +} + +.build-logs-section .log-chooser { + width: 25%; + min-width: 150px; + border-radius: 100px; + box-shadow: 0 0 25px 2px black; + color: #454343; + background-color: #c7da99; + padding-left: 10px; + margin: auto 0 auto auto; +} + +.build-logs-section .log-chooser input, +.build-logs-section .log-chooser input:focus:not(.browser-default) { + border-bottom: none; + margin-bottom: 0; +} + +.build-logs-section .small-screen, +.build-logs-section .fa-close { + display: none; +} + +.form-fields { + margin: auto 0 auto auto; + width: 60%; + padding-top: 10px; +} + +.search-field { + width: 60%; + min-width: 180px; +} + +.section-header { + display: flex; + align-items: center; +} + +.section-header form { + display: flex; +} + +@media only screen and (max-width: 660px) { + .build-logs-section .search-field { + display: none; + } + .build-logs-section .small-screen { + display: flex; + align-items: center; + margin: auto; + margin-right: 3px; + font-size: 2em; + } +} diff --git a/static/js/build_logs.js b/static/js/build_logs.js new file mode 100644 index 00000000..534db251 --- /dev/null +++ b/static/js/build_logs.js @@ -0,0 +1,102 @@ +$(document).ready(function(){ + $('select').formSelect(); + + var log_chooser_input = $('#log-chooser-input'); + var search_input = $('#search'); + var search_icon = $('.build-logs-section .small-screen'); + var close_icon = $('.build-logs-section .fa-close'); + var log_chooser_div = $('.build-logs-section .log-chooser'); + var search_field_div = $('.build-logs-section .search-field'); + var log_type = null; + var logs_data = null; + var searched_keyword = ''; + + function addLogsHTML(info){ + var info_el = $('

').text(info); + $('.build-logs').append(info_el); + } + + function updateBuildLogsHTML(){ + $('.build-logs p').remove(); + if(logs_data.length > 0) { + for(var entry in logs_data){ + if(logs_data[entry]){ + addLogsHTML(logs_data[entry]); + } + } + } + else { + var info = 'There are no log entries for tag ' + log_type + '.'; + addLogsHTML(info); + } + } + + function updateBuildLogs(type){ + $.getJSON("/static/ci-build-detailed-logs.json", function(data) { + log_type = type; + if(log_type === 'logs') { + logs_data = data[log_type]; + } + else { + logs_data = data.logs_level_Specific[log_type]; + } + updateBuildLogsHTML(); + }) + .fail(function(data, textStatus, error) { + var err = "Request Failed: " + textStatus + ", " + error; + console.error(err); + }); + } + + function searchBuildLogs(){ + var found = false; + var info = ''; + for(var entry in logs_data){ + if(logs_data[entry]){ + info = logs_data[entry]; + if(info.includes(searched_keyword)){ + found = true; + addLogsHTML(info); + } + } + } + if(!found){ + if(log_type === 'logs'){ + info = searched_keyword + ' not found in logs!'; + } + else { + info = searched_keyword + ' not found in ' + log_type + + ' level logs!'; + } + addLogsHTML(info); + } + } + + updateBuildLogs('logs'); + + log_chooser_input.on('change', function(){ + updateBuildLogs(log_chooser_input.val()); + }); + + search_input.on('keypress', function(key){ + if(key.which === 13){ + searched_keyword = search_input.val(); + $('.build-logs p').remove(); + searchBuildLogs(); + } + }); + + search_icon.on('click', function(){ + search_icon.css('display', 'none'); + close_icon.css('display', 'block'); + log_chooser_div.css('display', 'none'); + search_field_div.css('display', 'flex'); + }); + close_icon.on('click', function(){ + search_icon.css('display', 'flex'); + close_icon.css('display', 'none'); + log_chooser_div.css('display', 'flex'); + search_field_div.css('display', 'none'); + }); + +}); diff --git a/templates/base.html b/templates/base.html index cdc564a2..9638dfdc 100644 --- a/templates/base.html +++ b/templates/base.html @@ -29,7 +29,7 @@