diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/clusters/clusters_bundle.js | 13 | ||||
-rw-r--r-- | app/assets/javascripts/clusters/clusters_index.js | 67 | ||||
-rw-r--r-- | app/assets/javascripts/toggle_button.js | 100 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/toggle_button.vue | 1 | ||||
-rw-r--r-- | app/views/projects/clusters/_cluster.html.haml | 10 | ||||
-rw-r--r-- | app/views/projects/clusters/_integration_form.html.haml | 10 |
6 files changed, 123 insertions, 78 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 637d0dbde23..81b50cd919e 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -14,6 +14,7 @@ import { import ClustersService from './services/clusters_service'; import ClustersStore from './stores/clusters_store'; import applications from './components/applications.vue'; +import ToggleButton from '../toggle_button'; /** * Cluster page has 2 separate parts: @@ -48,12 +49,9 @@ export default class Clusters { installPrometheusEndpoint: installPrometheusPath, }); - this.toggle = this.toggle.bind(this); this.installApplication = this.installApplication.bind(this); this.showToken = this.showToken.bind(this); - this.toggleButton = document.querySelector('.js-toggle-cluster'); - this.toggleInput = document.querySelector('.js-toggle-input'); this.errorContainer = document.querySelector('.js-cluster-error'); this.successContainer = document.querySelector('.js-cluster-success'); this.creatingContainer = document.querySelector('.js-cluster-creating'); @@ -63,6 +61,8 @@ export default class Clusters { this.tokenField = document.querySelector('.js-cluster-token'); initSettingsPanels(); + const toggleButton = new ToggleButton(document.querySelector('.js-project-feature-toggle')); + toggleButton.init(); this.initApplications(); if (this.store.state.status !== 'created') { @@ -101,13 +101,11 @@ export default class Clusters { } addListeners() { - this.toggleButton.addEventListener('click', this.toggle); if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); eventHub.$on('installApplication', this.installApplication); } removeListeners() { - this.toggleButton.removeEventListener('click', this.toggle); if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken); eventHub.$off('installApplication', this.installApplication); } @@ -151,11 +149,6 @@ export default class Clusters { this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason); } - toggle() { - this.toggleButton.classList.toggle('is-checked'); - this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('is-checked').toString()); - } - showToken() { const type = this.tokenField.getAttribute('type'); diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js index 6844d1dbd83..d853a37672b 100644 --- a/app/assets/javascripts/clusters/clusters_index.js +++ b/app/assets/javascripts/clusters/clusters_index.js @@ -1,58 +1,21 @@ import Flash from '../flash'; import { s__ } from '../locale'; +import ToggleButton from '../toggle_button'; import ClustersService from './services/clusters_service'; -/** - * Toggles loading and disabled classes. - * @param {HTMLElement} button - */ -const toggleLoadingButton = (button) => { - if (button.getAttribute('disabled')) { - button.removeAttribute('disabled'); - } else { - button.setAttribute('disabled', true); - } - button.classList.toggle('is-loading'); -}; +export default () => { + document.querySelectorAll('.js-project-feature-toggle').forEach((toggle) => { + const endpoint = toggle.dataset.endpoint; -/** - * Toggles checked class for the given button - * @param {HTMLElement} button - */ -const toggleValue = (button) => { - button.classList.toggle('is-checked'); + const toggleButton = new ToggleButton( + toggle, + value => + ClustersService.updateCluster(endpoint, { cluster: { enabled: value } }) + .catch((err) => { + Flash(s__('ClusterIntegration|Something went wrong on our end.')); + throw err; + }), + ); + toggleButton.init(); + }); }; - -/** - * Handles toggle buttons in the cluster's table. - * - * When the user clicks the toggle button for each cluster, it: - * - toggles the button - * - shows a loading and disables button - * - Makes a put request to the given endpoint - * Once we receive the response, either: - * 1) Show updated status in case of successfull response - * 2) Show initial status in case of failed response - */ -export default function setClusterTableToggles() { - document.querySelectorAll('.js-toggle-cluster-list') - .forEach(button => button.addEventListener('click', (e) => { - const toggleButton = e.currentTarget; - const endpoint = toggleButton.getAttribute('data-endpoint'); - - toggleValue(toggleButton); - toggleLoadingButton(toggleButton); - - const value = toggleButton.classList.contains('is-checked'); - - ClustersService.updateCluster(endpoint, { cluster: { enabled: value } }) - .then(() => { - toggleLoadingButton(toggleButton); - }) - .catch(() => { - toggleLoadingButton(toggleButton); - toggleValue(toggleButton); - Flash(s__('ClusterIntegration|Something went wrong on our end.')); - }); - })); -} diff --git a/app/assets/javascripts/toggle_button.js b/app/assets/javascripts/toggle_button.js new file mode 100644 index 00000000000..790c9e0d48b --- /dev/null +++ b/app/assets/javascripts/toggle_button.js @@ -0,0 +1,100 @@ +import $ from 'jquery'; +import Vue from 'vue'; +import Flash from './flash'; +import { __ } from './locale'; +import { convertPermissionToBoolean } from './lib/utils/common_utils'; +import toggleButton from './vue_shared/components/toggle_button.vue'; + +/* + Example HAML: + ``` + %input{ type: "hidden", class: 'js-project-feature-toggle', value: enabled? } + ``` + + Example JS: + ``` + const toggleButton = new ToggleButton( + document.querySelector('.js-project-feature-toggle'), + (newValue, toggle) => { console.log('toggle clicked', newValue); }, + ); + toggleButton.init(); + ``` +*/ + +export default class ToggleButton { + constructor(toggle, clickCallback = $.noop) { + this.toggle = toggle; + this.clickCallback = clickCallback; + + this.state = { + name: toggle.getAttribute('name'), + value: convertPermissionToBoolean(toggle.value), + isDisabled: !!toggle.getAttribute('disabled'), + isLoading: false, + }; + } + + init() { + const state = this.state; + const onToggleClicked = this.onToggleClicked.bind(this); + + // eslint-disable-next-line no-new + new Vue({ + el: this.toggle, + components: { + toggleButton, + }, + data() { + return { + state, + }; + }, + render(createElement) { + return createElement('toggleButton', { + props: { + name: this.state.name, + value: this.state.value, + disabledInput: this.state.isDisabled, + isLoading: this.state.isLoading, + }, + on: { + change: onToggleClicked, + }, + }); + }, + }); + } + + onToggleClicked(newValue) { + // Visually change the toggle and start loading + this.setValue(newValue); + this.setDisabled(true); + this.setLoading(true); + + Promise.resolve(this.clickCallback(newValue)) + .catch(() => { + // Revert the visuals if something goes wrong + this.setValue(!newValue); + }) + .then(() => { + // Remove the loading indicator in any case + this.setDisabled(false); + this.setLoading(false); + }) + .catch(() => { + Flash(__('Something went wrong when toggling the button')); + }); + } + + setValue(value) { + this.state.value = value; + } + + setDisabled(value) { + this.state.isDisabled = value; + } + + setLoading(value) { + this.state.isLoading = value; + } +} diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue index 09031d3ffa1..245c088e183 100644 --- a/app/assets/javascripts/vue_shared/components/toggle_button.vue +++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue @@ -76,6 +76,7 @@ 'is-disabled': disabledInput, 'is-loading': isLoading }" + :disabled="disabledInput" @click="toggleFeature" > <loadingIcon class="loading-icon" /> diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/projects/clusters/_cluster.html.haml index 3943dfc0856..0b7cc831d44 100644 --- a/app/views/projects/clusters/_cluster.html.haml +++ b/app/views/projects/clusters/_cluster.html.haml @@ -12,12 +12,8 @@ .table-section.section-10 .table-mobile-header{ role: "rowheader" } .table-mobile-content - %button{ type: "button", - class: "js-toggle-cluster-list project-feature-toggle #{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}", - "aria-label": s_("ClusterIntegration|Toggle Cluster"), + %input{ type: "hidden", + class: 'js-project-feature-toggle', + value: cluster.enabled?, disabled: !cluster.can_toggle_cluster?, data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } } - = icon("spinner spin", class: "loading-icon") - %span.toggle-icon - = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') - = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/projects/clusters/_integration_form.html.haml index 9d593ffc021..3ca0092fa13 100644 --- a/app/views/projects/clusters/_integration_form.html.haml +++ b/app/views/projects/clusters/_integration_form.html.haml @@ -11,15 +11,7 @@ - else = s_('ClusterIntegration|Cluster integration is disabled for this project.') %label.append-bottom-10 - = field.hidden_field :enabled, { class: 'js-toggle-input'} - - %button{ type: 'button', - class: "js-toggle-cluster project-feature-toggle #{'is-checked' unless !@cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}", - "aria-label": s_("ClusterIntegration|Toggle Cluster"), - disabled: !can?(current_user, :update_cluster, @cluster) } - %span.toggle-icon - = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') - = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') + = field.hidden_field :enabled, { class: 'js-project-feature-toggle', disabled: !can?(current_user, :update_cluster, @cluster) } .form-group %h5= s_('ClusterIntegration|Environment scope') |