/** * Common code between environmets app and folder view */ import { isEqual, isFunction, omitBy } from 'lodash'; import Visibility from 'visibilityjs'; import { createAlert } from '~/flash'; import Poll from '~/lib/utils/poll'; import { getParameterByName } from '~/lib/utils/url_utility'; import { s__, __ } from '~/locale'; import tabs from '~/vue_shared/components/navigation_tabs.vue'; import tablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import container from '../components/container.vue'; import environmentTable from '../components/environments_table.vue'; import eventHub from '../event_hub'; import EnvironmentsService from '../services/environments_service'; import EnvironmentsStore from '../stores/environments_store'; export default { components: { environmentTable, container, tabs, tablePagination, }, data() { const store = new EnvironmentsStore(); const isDetailView = document.body.contains( document.getElementById('environments-detail-view'), ); return { store, state: store.state, isLoading: false, isMakingRequest: false, scope: getParameterByName('scope') || 'available', page: getParameterByName('page') || '1', requestData: {}, environmentInStopModal: {}, environmentInDeleteModal: {}, environmentInRollbackModal: {}, isDetailView, }; }, methods: { saveData(resp) { this.isLoading = false; // Prevent the absence of the nested flag from causing mismatches const response = this.filterNilValues(resp.config.params); const request = this.filterNilValues(this.requestData); if (isEqual(response, request)) { this.store.storeAvailableCount(resp.data.available_count); this.store.storeStoppedCount(resp.data.stopped_count); this.store.storeEnvironments(resp.data.environments); this.store.setReviewAppDetails(resp.data.review_app); this.store.setPagination(resp.headers); } }, filterNilValues(obj) { return omitBy(obj, (value) => value === undefined || value === null); }, /** * Handles URL and query parameter changes. * When the user uses the pagination or the tabs, * - update URL * - Make API request to the server with new parameters * - Update the polling function * - Update the internal state */ updateContent(parameters) { this.updateInternalState(parameters); // fetch new data return this.service .fetchEnvironments(this.requestData) .then((response) => { this.successCallback(response); this.poll.enable({ data: this.requestData, response }); }) .catch(() => { this.errorCallback(); // restart polling this.poll.restart(); }); }, errorCallback() { this.isLoading = false; createAlert({ message: s__('Environments|An error occurred while fetching the environments.'), }); }, postAction({ endpoint, errorMessage = s__('Environments|An error occurred while making the request.'), }) { if (!this.isMakingRequest) { this.isLoading = true; this.service .postAction(endpoint) .then(() => { // Originally, the detail page buttons were implemented as
s that POSTed // to the server, which would naturally result in a page refresh. // When environment details page was converted to Vue, the buttons were updated to trigger // HTTP requests using `axios`, which did not cause a refresh on completion. // To preserve the original behavior, we manually reload the page when // network requests complete successfully. if (!this.isDetailView) { this.fetchEnvironments(); } else { window.location.reload(); } }) .catch((err) => { this.isLoading = false; createAlert({ message: isFunction(errorMessage) ? errorMessage(err.response.data) : errorMessage, }); }); } }, fetchEnvironments() { this.isLoading = true; return this.service .fetchEnvironments(this.requestData) .then(this.successCallback) .catch(this.errorCallback); }, updateStopModal(environment) { this.environmentInStopModal = environment; }, updateDeleteModal(environment) { this.environmentInDeleteModal = environment; }, updateRollbackModal(environment) { this.environmentInRollbackModal = environment; }, stopEnvironment(environment) { const endpoint = environment.stop_path; const errorMessage = s__( 'Environments|An error occurred while stopping the environment, please try again', ); this.postAction({ endpoint, errorMessage }); }, deleteEnvironment(environment) { const endpoint = environment.delete_path; const { onSingleEnvironmentPage } = environment; const errorMessage = s__( 'Environments|An error occurred while deleting the environment. Check if the environment stopped; if not, stop it and try again.', ); this.service .deleteAction(endpoint) .then(() => { if (!onSingleEnvironmentPage) { // Reload as a first solution to bust the ETag cache window.location.reload(); return; } const url = window.location.href.split('/'); url.pop(); window.location.href = url.join('/'); }) .catch(() => { createAlert({ message: errorMessage, }); }); }, rollbackEnvironment(environment) { const { retryUrl, isLastDeployment } = environment; const errorMessage = isLastDeployment ? s__('Environments|An error occurred while re-deploying the environment, please try again') : s__( 'Environments|An error occurred while rolling back the environment, please try again', ); this.postAction({ endpoint: retryUrl, errorMessage }); }, cancelAutoStop(autoStopPath) { const errorMessage = ({ message }) => message || s__('Environments|An error occurred while canceling the auto stop, please try again'); this.postAction({ endpoint: autoStopPath, errorMessage }); }, }, computed: { tabs() { return [ { name: __('Available'), scope: 'available', count: this.state.availableCounter, isActive: this.scope === 'available', }, { name: __('Stopped'), scope: 'stopped', count: this.state.stoppedCounter, isActive: this.scope === 'stopped', }, ]; }, activeTab() { return this.tabs.findIndex(({ isActive }) => isActive) ?? 0; }, }, /** * Fetches all the environments and stores them. * Toggles loading property. */ created() { this.service = new EnvironmentsService(this.endpoint); this.requestData = { page: this.page, scope: this.scope, nested: true }; if (!this.isDetailView) { this.poll = new Poll({ resource: this.service, method: 'fetchEnvironments', data: this.requestData, successCallback: this.successCallback, errorCallback: this.errorCallback, notificationCallback: (isMakingRequest) => { this.isMakingRequest = isMakingRequest; }, }); if (!Visibility.hidden()) { this.isLoading = true; this.poll.makeRequest(); } else { this.fetchEnvironments(); } Visibility.change(() => { if (!Visibility.hidden()) { this.poll.restart(); } else { this.poll.stop(); } }); } eventHub.$on('postAction', this.postAction); eventHub.$on('requestStopEnvironment', this.updateStopModal); eventHub.$on('stopEnvironment', this.stopEnvironment); eventHub.$on('requestDeleteEnvironment', this.updateDeleteModal); eventHub.$on('deleteEnvironment', this.deleteEnvironment); eventHub.$on('requestRollbackEnvironment', this.updateRollbackModal); eventHub.$on('rollbackEnvironment', this.rollbackEnvironment); eventHub.$on('cancelAutoStop', this.cancelAutoStop); }, beforeDestroy() { eventHub.$off('postAction', this.postAction); eventHub.$off('requestStopEnvironment', this.updateStopModal); eventHub.$off('stopEnvironment', this.stopEnvironment); eventHub.$off('requestDeleteEnvironment', this.updateDeleteModal); eventHub.$off('deleteEnvironment', this.deleteEnvironment); eventHub.$off('requestRollbackEnvironment', this.updateRollbackModal); eventHub.$off('rollbackEnvironment', this.rollbackEnvironment); eventHub.$off('cancelAutoStop', this.cancelAutoStop); }, };