summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/clusters/clusters_index.js10
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss9
-rw-r--r--app/assets/stylesheets/framework/common.scss8
-rw-r--r--app/assets/stylesheets/framework/toggle.scss139
-rw-r--r--app/assets/stylesheets/pages/projects.scss87
-rw-r--r--app/controllers/projects/clusters_controller.rb2
-rw-r--r--app/views/projects/clusters/_empty_state.html.haml20
-rw-r--r--app/views/projects/clusters/index.html.haml36
-rw-r--r--spec/features/projects/clusters_spec.rb13
-rw-r--r--spec/javascripts/clusters/clusters_index_spec.js38
-rw-r--r--spec/javascripts/fixtures/clusters.rb9
12 files changed, 243 insertions, 129 deletions
diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js
index 52dfed5668a..efdf2de5583 100644
--- a/app/assets/javascripts/clusters/clusters_index.js
+++ b/app/assets/javascripts/clusters/clusters_index.js
@@ -47,9 +47,15 @@ export default class ClusterTable {
* @param {HTMLElement} button
*/
static toggleLoadingButton(button) {
- button.setAttribute('disabled', button.getAttribute('disabled'));
+ if (button.getAttribute('disabled')) {
+ button.removeAttribute('disabled');
+ } else {
+ button.setAttribute('disabled', true);
+ }
+
button.classList.toggle('disabled');
- button.classList.toggle('loading');
+ button.classList.toggle('is-loading');
+ button.querySelector('.loading-icon').classList.toggle('hidden');
}
/**
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 66212be1b8f..43b16d3cf7d 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -44,6 +44,7 @@
@import "framework/tabs";
@import "framework/timeline";
@import "framework/tooltips";
+@import "framework/toggle";
@import "framework/typography";
@import "framework/zen";
@import "framework/blank";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 17096e25207..91976ca1f56 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -348,3 +348,12 @@
}
}
}
+
+.flex-container-block {
+ display: -webkit-flex;
+ display: flex;
+}
+
+.flex-right {
+ margin-left: auto;
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 4316b1e87ae..5e4ddf366ef 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -454,11 +454,3 @@ img.emoji {
.inline { display: inline-block; }
.center { text-align: center; }
.vertical-align-middle { vertical-align: middle; }
-
-.flex-justify-content-center { justify-content: center; }
-.flex-wrap { flex-wrap: wrap; }
-.flex-right { margin-left: auto; }
-.flex-container-block {
- display: -webkit-flex;
- display: flex;
-}
diff --git a/app/assets/stylesheets/framework/toggle.scss b/app/assets/stylesheets/framework/toggle.scss
new file mode 100644
index 00000000000..e5ab604dab6
--- /dev/null
+++ b/app/assets/stylesheets/framework/toggle.scss
@@ -0,0 +1,139 @@
+/**
+* Toggle button
+*
+* @usage
+* ### Active text
+* <button type="button" class="project-feature-toggle checked" data-enabled-text="Enabled" data-disabled-text="Disabled">
+* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
+* </button>
+
+* ### Disabled text
+* <button type="button" class="project-feature-toggle" data-enabled-text="Enabled" data-disabled-text="Disabled">
+* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
+* </button>
+
+* ### Disabled button
+* <button type="button" class="project-feature-toggle disabled" data-enabled-text="Enabled" data-disabled-text="Disabled" disabled="true">
+* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
+* </button>
+
+* ### Loading
+* <button type="button" class="project-feature-toggle is-loading" data-enabled-text="Enabled" data-disabled-text="Disabled">
+* <i class="fa fa-spinner fa-spin loading-icon"></i>
+* </button>
+*/
+.project-feature-toggle {
+ position: relative;
+ border: 0;
+ outline: 0;
+ display: block;
+ width: 100px;
+ height: 24px;
+ cursor: pointer;
+ user-select: none;
+ background: $feature-toggle-color-disabled;
+ border-radius: 12px;
+ padding: 3px;
+ transition: all .4s ease;
+
+ &::selection,
+ &::before::selection,
+ &::after::selection {
+ background: none;
+ }
+
+ &::before {
+ color: $feature-toggle-text-color;
+ font-size: 12px;
+ line-height: 24px;
+ position: absolute;
+ top: 0;
+ left: 25px;
+ right: 5px;
+ text-align: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ animation: animate-disabled .2s ease-in;
+ content: attr(data-disabled-text);
+ }
+
+ &::after {
+ position: relative;
+ display: block;
+ content: "";
+ width: 22px;
+ height: 18px;
+ left: 0;
+ border-radius: 9px;
+ background: $feature-toggle-color;
+ transition: all .2s ease;
+ }
+
+ &.is-loading {
+ &::before {
+ left: 38px;
+ right: 5px;
+ }
+
+ .loading-icon {
+ position: absolute;
+ left: 28px;
+ font-size: $tooltip-font-size;
+ color: $white-light;
+ top: 6px;
+ }
+ }
+
+ &.checked {
+ background: $feature-toggle-color-enabled;
+
+ &.is-loading {
+ &::before {
+ left: 10px;
+ right: 42px;
+ animation: animate-enabled .2s ease-in;
+ content: attr(data-enabled-text);
+ }
+
+ .loading-icon {
+ left: 60px;
+ top: 6px;
+ }
+ }
+
+ &::before {
+ left: 5px;
+ right: 25px;
+ animation: animate-enabled .2s ease-in;
+ content: attr(data-enabled-text);
+ }
+
+ &::after {
+ left: calc(100% - 22px);
+ }
+ }
+
+ &.disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+
+ @media (max-width: $screen-xs-min) {
+ width: 50px;
+
+ &::before,
+ &.checked::before {
+ display: none;
+ }
+ }
+
+ @keyframes animate-enabled {
+ 0%, 35% { opacity: 0; }
+ 100% { opacity: 1; }
+ }
+
+ @keyframes animate-disabled {
+ 0%, 35% { opacity: 0; }
+ 100% { opacity: 1; }
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index aaad6dbba8e..baf3726c827 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -126,93 +126,6 @@
}
}
-.project-feature-toggle {
- position: relative;
- border: 0;
- outline: 0;
- display: block;
- width: 100px;
- height: 24px;
- cursor: pointer;
- user-select: none;
- background: $feature-toggle-color-disabled;
- border-radius: 12px;
- padding: 3px;
- transition: all .4s ease;
-
- &::selection,
- &::before::selection,
- &::after::selection {
- background: none;
- }
-
- &::before {
- color: $feature-toggle-text-color;
- font-size: 12px;
- line-height: 24px;
- position: absolute;
- top: 0;
- left: 25px;
- right: 5px;
- text-align: center;
- overflow: hidden;
- text-overflow: ellipsis;
- animation: animate-disabled .2s ease-in;
- content: attr(data-disabled-text);
- }
-
- &::after {
- position: relative;
- display: block;
- content: "";
- width: 22px;
- height: 18px;
- left: 0;
- border-radius: 9px;
- background: $feature-toggle-color;
- transition: all .2s ease;
- }
-
- &.checked {
- background: $feature-toggle-color-enabled;
-
- &::before {
- left: 5px;
- right: 25px;
- animation: animate-enabled .2s ease-in;
- content: attr(data-enabled-text);
- }
-
- &::after {
- left: calc(100% - 22px);
- }
- }
-
- &.disabled {
- opacity: 0.4;
- cursor: not-allowed;
- }
-
- @media (max-width: $screen-xs-min) {
- width: 50px;
-
- &::before,
- &.checked::before {
- display: none;
- }
- }
-
- @keyframes animate-enabled {
- 0%, 35% { opacity: 0; }
- 100% { opacity: 1; }
- }
-
- @keyframes animate-disabled {
- 0%, 35% { opacity: 0; }
- 100% { opacity: 1; }
- }
-}
-
.project-home-panel,
.group-home-panel {
padding-top: 24px;
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 37271c4708d..19dbffed5bb 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -8,6 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController
def index
@clusters ||= project.clusters.page(params[:page]).per(20).map { |cluster| cluster.present(current_user: current_user) }
+
+ @clusters_count = @clusters.count
end
def login
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml
index 4554f5c624d..07e26cd9021 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/projects/clusters/_empty_state.html.haml
@@ -1,10 +1,12 @@
-.empty-state.flex-justify-content-center.flex-container-block.flex-wrap
- %div
- %h2= s_('ClusterIntegration|Integrate cluster automation')
- - link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
- %p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
+.row.empty-state
+ .col-xs-12
+ .svg-content= image_tag 'illustrations/labels.svg'
+ .col-xs-12.text-center
+ .text-content
+ %h4= s_('ClusterIntegration|Integrate cluster automation')
+ - link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ %p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
+
+ %p
+ = link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success', title: 'Add cluster'
- %p
- = link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success', title: 'Add cluster'
- .svg-content
- = image_tag 'illustrations/labels.svg'
diff --git a/app/views/projects/clusters/index.html.haml b/app/views/projects/clusters/index.html.haml
index d7e0940cb65..be6efbaa38b 100644
--- a/app/views/projects/clusters/index.html.haml
+++ b/app/views/projects/clusters/index.html.haml
@@ -2,26 +2,26 @@
= render "empty_state"
- else
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
+ .fade-left= icon("angle-left")
+ .fade-right= icon("angle-right")
%ul.nav-links.scrolling-tabs
%li
- %a
- = s_('ClusterIntegration|Active')
+ %a.js-active-tab
+ =s_("ClusterIntegration|Active")
%span.badge
- 0
+ TODO
+
%li
- %a
- = s_('ClusterIntegration|Inactive')
+ %a.js-inactive-tab
+ = s_("ClusterIntegration|Inactive")
%span.badge
- 0
+ TODO
%li
- %a
- = s_('ClusterIntegration|All')
- %span.badge
- 0
+ %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', title: 'Add cluster'
+ = 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' }
@@ -35,17 +35,17 @@
.gl-responsive-table-row
.table-section.section-30
.table-mobile-header{ role: 'rowheader' }= s_('ClusterIntegration|Cluster')
- .table-mobile-content= cluster.name
+ .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
- Content goes here
+ .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
+ Content goes here - TODO
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }
.table-mobile-content
@@ -56,5 +56,5 @@
data: { 'enabled-text': 'Enabled',
'disabled-text': 'Disabled',
endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
- = icon('loading', class: 'hidden')
+ = icon('spinner spin', class: 'hidden loading-icon')
diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb
index dc78ac48cc9..3df4ef9d8c0 100644
--- a/spec/features/projects/clusters_spec.rb
+++ b/spec/features/projects/clusters_spec.rb
@@ -95,23 +95,32 @@ feature 'Clusters', :js do
end
it 'user sees a table with one cluster' do
-
+ expect(page).to have_selector('.gl-responsive-table-row', count: 2)
end
it 'user sees a disabled add cluster button ' do
-
+ expect(page.find(:css, '.js-add-cluster')['disabled']).to eq('true')
end
it 'user sees navigation tabs' do
+ expect(page.find('.js-active-tab').text).to include('Active')
+ expect(page.find('.js-active-tab .badge').text).to include('1')
+ expect(page.find('.js-inactive-tab').text).to include('Inactive')
+ expect(page.find('.js-inactive-tab .badge').text).to include('0')
+
+ expect(page.find('.js-all-tab').text).to include('All')
+ expect(page.find('.js-all-tab .badge').text).to include('1')
end
context 'update cluster' do
it 'user can update cluster' do
+ expect(page).to have_selector('.js-toggle-cluster-list')
end
context 'with sucessfull request' do
it 'user sees updated cluster' do
+
end
end
diff --git a/spec/javascripts/clusters/clusters_index_spec.js b/spec/javascripts/clusters/clusters_index_spec.js
index 8798f5c37f0..f0bc936a7d3 100644
--- a/spec/javascripts/clusters/clusters_index_spec.js
+++ b/spec/javascripts/clusters/clusters_index_spec.js
@@ -1,10 +1,17 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import ClusterTable from '~/clusters/clusters_index';
+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(() => {
@@ -13,19 +20,44 @@ describe('Clusters table', () => {
describe('update cluster', () => {
it('renders a toggle button', () => {
-
+ expect(document.querySelector('.js-toggle-cluster-list')).not.toBeNull();
});
it('renders loading state while request is made', () => {
+ const button = document.querySelector('.js-toggle-cluster-list');
+
+ button.click();
+
+ expect(button.classList).toContain('is-loading');
+ expect(button.classList).toContain('disabled');
+ });
+ afterEach(() => {
+ mock.restore();
});
- it('shows updated state after sucessfull request', () => {
+ it('shows updated state after sucessfull request', (done) => {
+ mock.onPut().reply(200, {}, {});
+ const button = document.querySelector('.js-toggle-cluster-list');
+
+ button.click();
+ setTimeout(() => {
+ expect(button.classList).toContain('is-loading');
+ done();
+ }, 0);
});
- it('shows inital state after failed request', () => {
+ it('shows inital state after failed request', (done) => {
+ mock.onPut().reply(500, {}, {});
+ const button = document.querySelector('.js-toggle-cluster-list');
+
+ button.click();
+ setTimeout(() => {
+ expect(button.classList).toContain('is-loading');
+ done();
+ }, 0);
});
});
});
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
index 8e74c4f859c..ea1b5d90f9f 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/javascripts/fixtures/clusters.rb
@@ -31,4 +31,13 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
expect(response).to be_success
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)
+ end
end