diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js index efdf2de55835df18e712c01ab542849f8c599580..6249943da43db2c10d1ea700ca56fba331dfe547 100644 --- a/app/assets/javascripts/clusters/clusters_index.js +++ b/app/assets/javascripts/clusters/clusters_index.js @@ -18,11 +18,11 @@ export default class ClusterTable { this.container = '.js-clusters-list'; document.querySelectorAll(`${this.container} .js-toggle-cluster-list`).forEach(button => button.addEventListener('click', e => ClusterTable.updateCluster(e))); } - - removeListeners() { - document.querySelectorAll(`${this.container} .js-toggle-cluster-list`).forEach(button => button.removeEventListener('click')); - } - + /** + * When the toggle button is clicked, + * updates the status and makes a request to the API to update the cluster + * @param {Event} e + */ static updateCluster(e) { const toggleButton = e.currentTarget; const value = toggleButton.classList.contains('checked').toString(); diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index e5b9e1f2de6881c13fb37010e816a2340dafd9e0..88395325fd238992afcae8649311565d78e71b86 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -8,3 +8,9 @@ // Wait for the Vue to kick-in and render the applications block min-height: 302px; } + +.clusters-container { + .nav-bar-right { + padding: $gl-padding-top $gl-padding; + } +} diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index eb93dfabf3acad1645bdd8638626ecb5b9b96f15..675f0dfb645601bc7b724e5808387f1c43992bb8 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -103,7 +103,7 @@ class Projects::ClustersController < Projects::ApplicationController def clusters scope = params[:scope] || :all - @clusters = ClustersFinder.new(project, user, scope).execute + @clusters = ClustersFinder.new(project, current_user, scope).execute end def create_params diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 66146e612637ab785aba5eb87c3692c466ccf975..2476e93a2f08b7ec6c6ada0b6d4342fedfc828ac 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -193,7 +193,7 @@ = nav_link(controller: :clusters) do = link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do %span - Cluster + Clusters - if project_nav_tab? :wiki = nav_link(controller: :wikis) do diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/projects/clusters/_cluster.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..98d08554af1ea8f0417ba23b474007324f2ed9a6 --- /dev/null +++ b/app/views/projects/clusters/_cluster.html.haml @@ -0,0 +1,22 @@ +.gl-responsive-table-row + .table-section.section-30 + .table-mobile-header{ role: 'rowheader' }= s_('ClusterIntegration|Cluster') + .table-mobile-content + = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster) + .table-section.section-30 + .table-mobile-header{ role: 'rowheader' }= s_('ClusterIntegration|Environment pattern') + .table-mobile-content= cluster.environment_scope + .table-section.section-30 + .table-mobile-header{ role: 'rowheader' }= s_('ClusterIntegration|Project namespace') + .table-mobile-content= cluster.platform_kubernetes&.namespace + .table-section.section-10 + .table-mobile-header{ role: 'rowheader' } + .table-mobile-content + %button{ type: 'button', + class: "js-toggle-cluster-list project-feature-toggle #{'checked' unless !cluster.enabled?} #{'disabled' unless can?(current_user, :update_cluster, cluster)}", + 'aria-label': s_('ClusterIntegration|Toggle Cluster'), + disabled: !can?(current_user, :update_cluster, cluster), + data: { 'enabled-text': 'Enabled', + 'disabled-text': 'Disabled', + endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } } + = icon('spinner spin', class: 'hidden loading-icon') diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml index 07e26cd90218ae4775a58ccc388c1bf288defa50..24454012845c9954ec008264f832c29e02841363 100644 --- a/app/views/projects/clusters/_empty_state.html.haml +++ b/app/views/projects/clusters/_empty_state.html.haml @@ -1,6 +1,6 @@ .row.empty-state .col-xs-12 - .svg-content= image_tag 'illustrations/labels.svg' + .svg-content= image_tag 'illustrations/clusters_empty.svg' .col-xs-12.text-center .text-content %h4= s_('ClusterIntegration|Integrate cluster automation') diff --git a/app/views/projects/clusters/_tabs.html.haml b/app/views/projects/clusters/_tabs.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..87b86b02e5adc5ac782c5793ba32863887939a13 --- /dev/null +++ b/app/views/projects/clusters/_tabs.html.haml @@ -0,0 +1,19 @@ +.top-area.scrolling-tabs-container.inner-page-scroll-tabs + .fade-left= icon("angle-left") + .fade-right= icon("angle-right") + %ul.nav-links.scrolling-tabs + %li{ class: ('active' if @scope == 'enabled') }> + = link_to project_clusters_path(@project, scope: :enabled), class: "js-active-tab" do + = s_("ClusterIntegration|Active") + %span.badge= @active_count + + %li{ class: ('active' if @scope == 'disabled') }> + = link_to project_clusters_path(@project, scope: :disabled), class: "js-inactive-tab #{'active' if @scope == 'disabled'}" do + = s_("ClusterIntegration|Inactive") + %span.badge= @inactive_count + %li{ class: ('active' if @scope.nil? || @scope == 'all') }> + = link_to project_clusters_path(@project, scope: :all), class: "js-all-tab #{'active' if @scope == 'all' || @scope.nil?}" do + = s_("ClusterIntegration|All") + %span.badge= @inactive_count + .pull-right.nav-bar-right + = link_to s_("ClusterIntegration|Add cluster"), new_project_cluster_path(@project), class: "btn btn-success disabled has-tooltip js-add-cluster", title: s_("ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate") diff --git a/app/views/projects/clusters/index.html.haml b/app/views/projects/clusters/index.html.haml index be6efbaa38b923dc555bbfbd263f31c32bee3db9..d34601f3e3fe7c015855c031417fde4fb4669c70 100644 --- a/app/views/projects/clusters/index.html.haml +++ b/app/views/projects/clusters/index.html.haml @@ -1,60 +1,20 @@ -- if @clusters.empty? - = render "empty_state" -- else - .top-area.scrolling-tabs-container.inner-page-scroll-tabs - .fade-left= icon("angle-left") - .fade-right= icon("angle-right") - %ul.nav-links.scrolling-tabs - %li - %a.js-active-tab - =s_("ClusterIntegration|Active") - %span.badge - TODO - - %li - %a.js-inactive-tab - = s_("ClusterIntegration|Inactive") - %span.badge - TODO - %li - %a.js-all-tab - = s_("ClusterIntegration|All") - %span.badge= @clusters_count - .nav-controls - = link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: "btn btn-success disabled has-tooltip js-add-cluster", title: s_("ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate") - .ci-table.js-clusters-list - .gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-30{ role: 'rowheader' } - = s_('ClusterIntegration|Cluster') - .table-section.section-30{ role: 'rowheader' } - = s_('ClusterIntegration|Environment pattern') - .table-section.section-30{ role: 'rowheader' } - = s_('ClusterIntegration|Project namespace') - .table-section.section-10{ role: 'rowheader' } - - @clusters.each do |cluster| - .gl-responsive-table-row - .table-section.section-30 - .table-mobile-header{ role: 'rowheader' }= s_('ClusterIntegration|Cluster') - .table-mobile-content - = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster) - .table-section.section-30 - .table-mobile-header{ role: 'rowheader' } - = s_('ClusterIntegration|Environment pattern') - .table-mobile-content= cluster.environment_scope - .table-section.section-30 - .table-mobile-header{ role: 'rowheader' } - = s_('ClusterIntegration|Project namespace') - .table-mobile-content - Content goes here - TODO - .table-section.section-10 - .table-mobile-header{ role: 'rowheader' } - .table-mobile-content - %button{ type: 'button', - class: "js-toggle-cluster-list project-feature-toggle #{'checked' unless !cluster.enabled?} #{'disabled' unless can?(current_user, :update_cluster, cluster)}", - 'aria-label': s_('ClusterIntegration|Toggle Cluster'), - disabled: !can?(current_user, :update_cluster, cluster), - data: { 'enabled-text': 'Enabled', - 'disabled-text': 'Disabled', - endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } } - = icon('spinner spin', class: 'hidden loading-icon') +- breadcrumb_title "Clusters" +- page_title "Clusters" +.clusters-container + - if @clusters.empty? + = render "empty_state" + - else + = render "tabs" + .ci-table.js-clusters-list + .gl-responsive-table-row.table-row-header{ role: "row" } + .table-section.section-30{ role: "rowheader" } + = s_("ClusterIntegration|Cluster") + .table-section.section-30{ role: "rowheader" } + = s_("ClusterIntegration|Environment pattern") + .table-section.section-30{ role: "rowheader" } + = s_("ClusterIntegration|Project namespace") + .table-section.section-10{ role: "rowheader" } + - @clusters.each do |cluster| + = render "cluster", cluster: cluster + = paginate @clusters, theme: 'gitlab' diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index 3df4ef9d8c04736d1300bc6b8d722fa2a0bf033a..a3fc72ba64c94e707eacf6cf86734db2bfa54a53 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -24,8 +24,8 @@ feature 'Clusters', :js do visit project_clusters_path(project) end - it 'user sees a new page' do - expect(page).to have_button('Add cluster') + it 'user sees empty state' do + expect(page).to have_link('Add cluster') end context 'when user opens opens create on gke page' do @@ -99,7 +99,7 @@ feature 'Clusters', :js do end it 'user sees a disabled add cluster button ' do - expect(page.find(:css, '.js-add-cluster')['disabled']).to eq('true') + expect(page).to have_selector('.js-add-cluster.disabled') end it 'user sees navigation tabs' do @@ -244,7 +244,7 @@ feature 'Clusters', :js do before do visit project_clusters_path(project) - click_button 'Add cluster' + click_link 'Add cluster' click_link 'Create on GKE' end diff --git a/spec/javascripts/clusters/clusters_index_spec.js b/spec/javascripts/clusters/clusters_index_spec.js index f0bc936a7d3dc016ac6888db951dda080c8983dc..b8d082208a5455f9c89c21260fb3979f19bbec91 100644 --- a/spec/javascripts/clusters/clusters_index_spec.js +++ b/spec/javascripts/clusters/clusters_index_spec.js @@ -5,17 +5,12 @@ import { setTimeout } from 'core-js/library/web/timers'; describe('Clusters table', () => { preloadFixtures('clusters/index_cluster.html.raw'); - let ClustersClass; let mock; beforeEach(() => { loadFixtures('clusters/index_cluster.html.raw'); - ClustersClass = new ClusterTable(); mock = new MockAdapter(axios); - }); - - afterEach(() => { - ClustersClass.removeListeners(); + return new ClusterTable(); }); describe('update cluster', () => { @@ -39,11 +34,13 @@ describe('Clusters table', () => { it('shows updated state after sucessfull request', (done) => { mock.onPut().reply(200, {}, {}); const button = document.querySelector('.js-toggle-cluster-list'); - button.click(); + expect(button.classList).toContain('is-loading'); + setTimeout(() => { - expect(button.classList).toContain('is-loading'); + expect(button.classList).not.toContain('is-loading'); + expect(button.classList).not.toContain('checked'); done(); }, 0); }); @@ -53,9 +50,11 @@ describe('Clusters table', () => { const button = document.querySelector('.js-toggle-cluster-list'); button.click(); + expect(button.classList).toContain('is-loading'); setTimeout(() => { - expect(button.classList).toContain('is-loading'); + expect(button.classList).not.toContain('is-loading'); + expect(button.classList).toContain('checked'); done(); }, 0); }); diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index ea1b5d90f9f7dca29a11322334b5d26351d86cd6..d26ea3febe8a5a602d3d6c8e608a5b8c9ad24e4a 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -32,12 +32,18 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle store_frontend_fixture(response, example.description) end - it 'clusters/index_cluster.html.raw' do |example| - get :index, - namespace_id: project.namespace.to_param, - project_id: project - - expect(response).to be_success - store_frontend_fixture(response, example.description) + context 'rendering non-empty state' do + before do + cluster + end + + it 'clusters/index_cluster.html.raw' do |example| + get :index, + namespace_id: namespace, + project_id: project + + expect(response).to be_success + store_frontend_fixture(response, example.description) + end end end