diff --git a/src/api/app/assets/javascripts/webui2/application.js b/src/api/app/assets/javascripts/webui2/application.js index b38215c100b..11a577038e9 100644 --- a/src/api/app/assets/javascripts/webui2/application.js +++ b/src/api/app/assets/javascripts/webui2/application.js @@ -41,3 +41,4 @@ //= require webui2/jquery-ui.min.js //= require webui2/cm2/use-codemirror.js //= require webui2/package-view_file.js +//= require webui2/project_monitor.js diff --git a/src/api/app/assets/javascripts/webui2/datatables.js b/src/api/app/assets/javascripts/webui2/datatables.js index 2d540e13a41..2df70015e65 100644 --- a/src/api/app/assets/javascripts/webui2/datatables.js +++ b/src/api/app/assets/javascripts/webui2/datatables.js @@ -2,6 +2,8 @@ //= require datatables/dataTables.bootstrap4 //= require datatables/extensions/Responsive/dataTables.responsive //= require datatables/extensions/Responsive/responsive.bootstrap4 +//= require datatables/extensions/FixedColumns/fixedColumns.bootstrap4 +//= require datatables/extensions/FixedColumns/dataTables.fixedColumns function initializeDataTable(cssSelector, params){ // jshint ignore:line var defaultParams = { @@ -10,4 +12,3 @@ function initializeDataTable(cssSelector, params){ // jshint ignore:line var newParams = $.extend(defaultParams, params); $(cssSelector).DataTable(newParams); } - diff --git a/src/api/app/assets/javascripts/webui2/project_monitor.js b/src/api/app/assets/javascripts/webui2/project_monitor.js new file mode 100644 index 00000000000..b95b823da80 --- /dev/null +++ b/src/api/app/assets/javascripts/webui2/project_monitor.js @@ -0,0 +1,105 @@ +function hideRepositoryColumns() { + var table = $('#project-monitor-table').DataTable(); + + var repositories = []; + $('#project-monitor-repositories-dropdown input:checked').each(function () { + repositories.push($(this).val().trim()); + }); + + if (repositories.length === 0) { + return; + } + + var toShowColumns = []; + var offSet = 0; + $('#project-monitor-table thead tr:eq(0) th').each(function () { + var column = $(this); + var colSpan = column.prop('colspan'); + if (repositories.includes(column.text().trim())) { + var range = []; + for (var i = offSet; i < offSet + colSpan; i++) { + range.push(i); + } + toShowColumns = toShowColumns.concat(range); + } + offSet += colSpan; + }); + + table.columns().every(function () { + var index = this.index(); + if (index === 0) return; + this.visible(toShowColumns.includes(index)); + }); +} + +function hideArchitectureColumns() { + var table = $('#project-monitor-table').DataTable(); + + var architectures = []; + $('#project-monitor-architectures-dropdown input:checked').each(function () { + architectures.push($(this).val()); + }); + + if (architectures.length === 0) { + return; + } + + table.columns().every(function () { + if (this.index() === 0) return; + if (!this.visible()) return; + + var title = $(this.header()).text().trim(); + this.visible(architectures.includes(title)); + }); +} + +function updateStatusSearch() { + var table = $('#project-monitor-table').DataTable(); + var searchInput = $('#project-monitor_filter input'); + + var terms = []; + $('#project-monitor-status-dropdown input:checked').each(function () { + terms.push($(this).val()); + }); + + var searchTerm = terms.join("|"); + searchInput.val(searchTerm); + table.search(searchTerm).draw(); +} + +function updateMonitorFilters() { + updateStatusSearch(); + var table = $('#project-monitor-table').DataTable(); + table.columns().visible(true); + hideRepositoryColumns(); + hideArchitectureColumns(); +} + +function setupProjectMonitor() { // jshint ignore:line + initializeDataTable('#project-monitor-table', { // jshint ignore:line + scrollX: true, + scrollY: "50vh", + fixedColumns: true, + pageLength: 50, + search: { + regex: true, + smart: false, + } + }); + + $('[data-toggle="popover"]').popover({ trigger: 'hover click' }); + + $('#project-monitor-status-dropdown input:checkbox').on('change', function () { + updateStatusSearch(); + }); + + $('.monitor-no-filter-link').on('click', function () { + $(this).siblings().children('input:checked').prop('checked', false); + updateMonitorFilters(); + + }); + + $('#project-monitor-architectures-dropdown input:checkbox, #project-monitor-repositories-dropdown input:checkbox').on('change', function () { + updateMonitorFilters(); + }); +} \ No newline at end of file diff --git a/src/api/app/assets/stylesheets/webui2/datatables.scss b/src/api/app/assets/stylesheets/webui2/datatables.scss index ed8f4171a5f..73b61a2c525 100644 --- a/src/api/app/assets/stylesheets/webui2/datatables.scss +++ b/src/api/app/assets/stylesheets/webui2/datatables.scss @@ -1,5 +1,6 @@ @import 'datatables/dataTables.bootstrap4'; @import 'datatables/extensions/Responsive/responsive.bootstrap4'; +@import 'datatables/extensions/FixedColumns/fixedColumns.bootstrap4'; table.table-fixed { table-layout: fixed diff --git a/src/api/app/assets/stylesheets/webui2/modals.scss b/src/api/app/assets/stylesheets/webui2/modals.scss index 8d0c0bee2e6..3950ade5fc4 100644 --- a/src/api/app/assets/stylesheets/webui2/modals.scss +++ b/src/api/app/assets/stylesheets/webui2/modals.scss @@ -3,7 +3,3 @@ overflow-x: hidden; overflow-wrap: break-word; } - -.modal-body p { - white-space: pre-wrap; -} diff --git a/src/api/app/controllers/webui/project_controller.rb b/src/api/app/controllers/webui/project_controller.rb index db054be70c7..e3dc97afa35 100644 --- a/src/api/app/controllers/webui/project_controller.rb +++ b/src/api/app/controllers/webui/project_controller.rb @@ -396,6 +396,14 @@ def move_path end def monitor + if params.keys.length < 4 + @activate_client_search = true + else + flash[:notice] = "Limited search results, click #{view_context.link_to('here', project_monitor_path(@project))} to remove the filter." + end + + @legend = Buildresult::STATUS_DESCRIPTION + @name_filter = params[:pkgname] @lastbuild_switch = params[:lastbuild] if params[:defaults] @@ -454,6 +462,7 @@ def monitor @repohash[repo].delete(arch) unless has_packages end end + switch_to_webui2 end # should be in the package controller, but all the helper functions to render the result of a build are in the project diff --git a/src/api/app/views/webui2/webui/project/_monitor_control.html.haml b/src/api/app/views/webui2/webui/project/_monitor_control.html.haml new file mode 100644 index 00000000000..e6e2750be5c --- /dev/null +++ b/src/api/app/views/webui2/webui/project/_monitor_control.html.haml @@ -0,0 +1,58 @@ +.row + .col-md-12 + - if activate_client_search + %span.dropdown#project-monitor-status-dropdown + %button.btn.btn-outline-secondary.dropdown-toggle{ data: { toggle: :dropdown }, type: :button } + %span.caret + Status + .dropdown-menu + %button.btn.btn-link.monitor-no-filter-link No filter + - status.each do |status| + .custom-control.custom-checkbox.dropdown-item.ml-2 + %input.custom-control-input{ type: :checkbox, id: "#{status}-checkbox".parameterize, checked: false, value: status } + %label.custom-control-label{ for: "#{status}-checkbox".parameterize } + = status + %span.dropdown#project-monitor-architectures-dropdown + %button.btn.btn-outline-secondary.dropdown-toggle{ data: { toggle: :dropdown }, type: :button } + %span.caret + Architecture + .dropdown-menu + %button.btn.btn-link.monitor-no-filter-link No filter + - architectures.each do |architecture| + .custom-control.custom-checkbox.dropdown-item.ml-2 + %input.custom-control-input{ type: :checkbox, id: "#{architecture}-checkbox".parameterize, checked: false, value: architecture } + %label.custom-control-label{ for: "#{architecture}-checkbox".parameterize } + = architecture + %span.dropdown#project-monitor-repositories-dropdown + %button.btn.btn-outline-secondary.dropdown-toggle{ data: { toggle: :dropdown }, type: :button } + %span.caret + Repository + .dropdown-menu + %button.btn.btn-link.monitor-no-filter-link No filter + - repositories.each do |repository| + .custom-control.custom-checkbox.dropdown-item.ml-2 + %input.custom-control-input{ type: :checkbox, id: "#{repository}-checkbox".parameterize, checked: false, value: repository } + %label.custom-control-label{ for: "#{repository}-checkbox".parameterize } + = repository + %button.btn.btn-outline-secondary{ data: { toggle: 'modal', target: '#build-monitor-legend' }, title: 'Build status legend' } + Legend + - unless activate_client_search + .float-right + %a.btn.btn-outline-primary{ href: project_monitor_path(project) } + Remove filter + +.modal.fade#build-monitor-legend{ tabindex: -1, role: 'dialog', aria: { labelledby: 'confirm-modal-label', hidden: true } } + .modal-dialog.modal-dialog-centered.modal-lg{ role: 'document' } + .modal-content + .modal-header + %h5.modal-title + Build status legend + .modal-body + - @legend.each do |status, description| + %p + %strong + #{status}: + = description + .modal-footer + %button.btn.btn-sm.btn-outline-secondary.px-4{ data: { dismiss: 'modal' } } + Cancel diff --git a/src/api/app/views/webui2/webui/project/_tabs.html.haml b/src/api/app/views/webui2/webui/project/_tabs.html.haml index 2ecca04ac58..5031d3aa0bd 100644 --- a/src/api/app/views/webui2/webui/project/_tabs.html.haml +++ b/src/api/app/views/webui2/webui/project/_tabs.html.haml @@ -11,6 +11,8 @@ - unless project.defines_remote_instance? || project.is_maintenance? %li.nav-item = tab_link('Repositories', repositories_path) + %li.nav-item + = tab_link('Monitor', project_monitor_path(project)) %li.nav-item = tab_link('Requests', project_requests_path) - unless project.defines_remote_instance? diff --git a/src/api/app/views/webui2/webui/project/monitor.html.haml b/src/api/app/views/webui2/webui/project/monitor.html.haml new file mode 100644 index 00000000000..795356d72bd --- /dev/null +++ b/src/api/app/views/webui2/webui/project/monitor.html.haml @@ -0,0 +1,37 @@ +:ruby + @pagetitle = "Show #{@project}" + @layouttype = 'custom' + +.card.mb-3 + = render partial: 'tabs', locals: { project: @project } + .card-body#project-monitor + = render partial: 'monitor_control', + locals: { project: @project, activate_client_search: @activate_client_search, + status: @avail_status_values, repositories: @avail_repo_values, architectures: @avail_arch_values } + .row.mt-4 + .col-md-12.obs-dataTable + %table.table.table-striped.table-bordered.text-nowrap.w-100#project-monitor-table + %thead.header + %tr + %th + - @repohash.sort.each do |repo, archlist| + - next if archlist.empty? + %th.text-center{ colspan: archlist.length } + = repo + %tr + %th + - @repohash.sort.each do |_repo, archlist| + - archlist.sort.each do |arch| + %th + = arch + %tbody + - @packagenames.each do |packname| + %tr + %td + = link_to word_break(packname, 40), controller: :package, action: :show, package: packname, project: @project.to_s + - @repohash.sort.each do |repo, archlist| + - archlist.sort.each do |arch| + %td + = webui2_arch_repo_table_cell(repo, arch, packname, nil, false) +:javascript + setupProjectMonitor(); diff --git a/src/api/spec/bootstrap/features/webui/projects/monitor_spec.rb b/src/api/spec/bootstrap/features/webui/projects/monitor_spec.rb new file mode 100644 index 00000000000..9aae9e9305b --- /dev/null +++ b/src/api/spec/bootstrap/features/webui/projects/monitor_spec.rb @@ -0,0 +1,101 @@ +require 'browser_helper' +require 'bootstrap/support/page/monitor_page' + +RSpec.feature 'Monitor', type: :feature, js: true do + describe 'monitor' do + let(:admin_user) { create(:admin_user) } + let!(:project) { create(:project, name: 'TestProject') } + let!(:package1) { create(:package, project: project, name: 'TestPackage') } + let!(:package2) { create(:package, project: project, name: 'SecondPackage') } + let!(:repository1) { create(:repository, project: project, name: 'openSUSE_Tumbleweed', architectures: ['x86_64', 'i586']) } + let!(:repository2) { create(:repository, project: project, name: 'openSUSE_Leap_42.3', architectures: ['x86_64', 'i586']) } + let!(:repository3) { create(:repository, project: project, name: 'openSUSE_Leap_42.2', architectures: ['x86_64', 'i586']) } + + let(:build_results_xml) do + <<-XML + + + +
no source uploaded
+
+ +
no source uploaded
+
+
+ + +
no source uploaded
+
+ +
no source uploaded
+
+
+ + +
no source uploaded
+
+ +
no source uploaded
+
+
+ + +
no source uploaded
+
+ +
no source uploaded
+
+
+ + +
no source uploaded
+
+ +
no source uploaded
+
+
+ + +
no source uploaded
+
+ +
no source uploaded
+
+
+
+ XML + end + + before do + login admin_user + allow(Backend::Api::BuildResults::Status).to receive(:result_swiss_knife).and_return(build_results_xml) + visit project_monitor_path(project.name) + end + + scenario 'filtering build results by architecture' do + page = Page::MonitorPage.new(:architectures) + page.filter('i586') + + expect(page).to have_column('i586') + expect(page).not_to have_column('x86_64') + end + + scenario 'filtering build results by repository' do + page = Page::MonitorPage.new(:repositories) + page.filter('openSUSE_Leap_42.2') + page.filter('openSUSE_Leap_42.3') + + expect(page).to have_column('openSUSE_Leap_42.2') + expect(page).to have_column('openSUSE_Leap_42.3') + expect(page).not_to have_column('Tumbleweed') + end + + scenario 'filtering build results by status' do + page = Page::MonitorPage.new(:status) + page.filter('succeeded') + + expect(page).to have_row('TestPackage') + expect(page).not_to have_row('SecondPackage') + end + end +end diff --git a/src/api/spec/bootstrap/support/page/monitor_page.rb b/src/api/spec/bootstrap/support/page/monitor_page.rb new file mode 100644 index 00000000000..0788cd0fa2b --- /dev/null +++ b/src/api/spec/bootstrap/support/page/monitor_page.rb @@ -0,0 +1,34 @@ +module Page + class MonitorPage + include Capybara::DSL + + def initialize(filter_name) + @filter_name = filter_name + end + + def filter(element) + find("#project-monitor-#{filter_name}-dropdown").click + find(:css, "label[for='#{element.parameterize}-checkbox']").click + end + + def has_column?(column) + header.has_text?(column) + end + + def has_row?(row) + rows.has_text?(row) + end + + private + + attr_reader :filter_name + + def rows + find('table#project-monitor-table') + end + + def header + find('.dataTables_scrollHead') + end + end +end diff --git a/src/api/spec/features/webui/projects_spec.rb b/src/api/spec/features/webui/projects_spec.rb index 365b3c2839f..bf28b098a5f 100644 --- a/src/api/spec/features/webui/projects_spec.rb +++ b/src/api/spec/features/webui/projects_spec.rb @@ -506,6 +506,7 @@ end scenario 'filtering build results by package name' do + skip_if_bootstrap # this is now handled by datatables, we don't need to test it fill_in 'pkgname', with: package1.name click_button 'Filter:' @@ -515,6 +516,7 @@ end scenario 'filtering build results by architecture' do + skip_if_bootstrap find('#archlink').click uncheck 'arch_x86_64' click_button 'Filter:' @@ -525,6 +527,7 @@ end scenario 'filtering build results by repository' do + skip_if_bootstrap find('#repolink').click uncheck 'repo_openSUSE_Leap_42_2' uncheck 'repo_openSUSE_Leap_42_3' @@ -537,6 +540,7 @@ end scenario 'filtering build results by last build' do + skip_if_bootstrap # we don't support this anymore check 'lastbuild' click_button 'Filter:'