diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-25 09:08:11 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-25 09:08:11 +0000 |
commit | 5064bf8c5647d4c4430cbb4d097cf1592416de29 (patch) | |
tree | d051bf2abe2cc7061b3a7facb6669a56ccb9cf54 /app | |
parent | 9c83aadd2604e7e6cb1f84683f951e6b12872618 (diff) | |
download | gitlab-ce-5064bf8c5647d4c4430cbb4d097cf1592416de29.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
29 files changed, 455 insertions, 111 deletions
diff --git a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue index 7f0c232eea8..7418ca9edfc 100644 --- a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue +++ b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue @@ -1,7 +1,6 @@ <script> import { GlPopover, GlSprintf, GlButton, GlIcon } from '@gitlab/ui'; -import Cookies from 'js-cookie'; -import { parseBoolean, scrollToElement } from '~/lib/utils/common_utils'; +import { parseBoolean, scrollToElement, setCookie, getCookie } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; import { glEmojiTag } from '~/emoji'; import Tracking from '~/tracking'; @@ -51,7 +50,7 @@ export default { }, data() { return { - popoverDismissed: parseBoolean(Cookies.get(this.dismissKey)), + popoverDismissed: parseBoolean(getCookie(`${this.trackLabel}_${this.dismissKey}`)), tracking: { label: this.trackLabel, property: this.humanAccess, @@ -68,17 +67,27 @@ export default { emoji() { return popoverStates[this.trackLabel].emoji || ''; }, + dismissCookieName() { + return `${this.trackLabel}_${this.dismissKey}`; + }, + commitCookieName() { + return `suggest_gitlab_ci_yml_commit_${this.dismissKey}`; + }, }, mounted() { - if (this.trackLabel === 'suggest_commit_first_project_gitlab_ci_yml' && !this.popoverDismissed) + if ( + this.trackLabel === 'suggest_commit_first_project_gitlab_ci_yml' && + !this.popoverDismissed + ) { scrollToElement(document.querySelector(this.target)); + } this.trackOnShow(); }, methods: { onDismiss() { this.popoverDismissed = true; - Cookies.set(this.dismissKey, this.popoverDismissed, { expires: 365 }); + setCookie(this.dismissCookieName, this.popoverDismissed); }, trackOnShow() { if (!this.popoverDismissed) this.track(); diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index f4ce98037c8..5a77896f5ef 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -5,6 +5,7 @@ import NewCommitForm from '../new_commit_form'; import EditBlob from './edit_blob'; import BlobFileDropzone from '../blob/blob_file_dropzone'; import initPopover from '~/blob/suggest_gitlab_ci_yml'; +import { setCookie } from '~/lib/utils/common_utils'; export default () => { const editBlobForm = $('.js-edit-blob-form'); @@ -60,6 +61,16 @@ export default () => { } if (suggestEl) { + const commitButton = document.querySelector('#commit-changes'); + initPopover(suggestEl); + + if (commitButton) { + const commitCookieName = `suggest_gitlab_ci_yml_commit_${suggestEl.dataset.dismissKey}`; + + commitButton.addEventListener('click', () => { + setCookie(commitCookieName, true); + }); + } } }; diff --git a/app/assets/javascripts/environments/components/delete_environment_modal.vue b/app/assets/javascripts/environments/components/delete_environment_modal.vue new file mode 100644 index 00000000000..f731dc49a5b --- /dev/null +++ b/app/assets/javascripts/environments/components/delete_environment_modal.vue @@ -0,0 +1,66 @@ +<script> +import { GlTooltipDirective } from '@gitlab/ui'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; +import { s__, sprintf } from '~/locale'; +import eventHub from '../event_hub'; + +export default { + id: 'delete-environment-modal', + name: 'DeleteEnvironmentModal', + + components: { + GlModal, + }, + + directives: { + GlTooltip: GlTooltipDirective, + }, + + props: { + environment: { + type: Object, + required: true, + }, + }, + + computed: { + confirmDeleteMessage() { + return sprintf( + s__( + `Environments|Deleting the '%{environmentName}' environment cannot be undone. Do you want to delete it anyway?`, + ), + { + environmentName: this.environment.name, + }, + false, + ); + }, + }, + + methods: { + onSubmit() { + eventHub.$emit('deleteEnvironment', this.environment); + }, + }, +}; +</script> + +<template> + <gl-modal + :id="$options.id" + :footer-primary-button-text="s__('Environments|Delete environment')" + footer-primary-button-variant="danger" + @submit="onSubmit" + > + <template slot="header"> + <h4 class="modal-title d-flex mw-100"> + {{ __('Delete') }} + <span v-gl-tooltip :title="environment.name" class="text-truncate mx-1 flex-fill"> + {{ environment.name }}? + </span> + </h4> + </template> + + <p>{{ confirmDeleteMessage }}</p> + </gl-modal> +</template> diff --git a/app/assets/javascripts/environments/components/environment_delete.vue b/app/assets/javascripts/environments/components/environment_delete.vue new file mode 100644 index 00000000000..b53c5fa6583 --- /dev/null +++ b/app/assets/javascripts/environments/components/environment_delete.vue @@ -0,0 +1,70 @@ +<script> +/** + * Renders the delete button that allows deleting a stopped environment. + * Used in the environments table and the environment detail view. + */ + +import $ from 'jquery'; +import { GlTooltipDirective } from '@gitlab/ui'; +import Icon from '~/vue_shared/components/icon.vue'; +import { s__ } from '~/locale'; +import eventHub from '../event_hub'; +import LoadingButton from '../../vue_shared/components/loading_button.vue'; + +export default { + components: { + Icon, + LoadingButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + environment: { + type: Object, + required: true, + }, + }, + data() { + return { + isLoading: false, + }; + }, + computed: { + title() { + return s__('Environments|Delete environment'); + }, + }, + mounted() { + eventHub.$on('deleteEnvironment', this.onDeleteEnvironment); + }, + beforeDestroy() { + eventHub.$off('deleteEnvironment', this.onDeleteEnvironment); + }, + methods: { + onClick() { + $(this.$el).tooltip('dispose'); + eventHub.$emit('requestDeleteEnvironment', this.environment); + }, + onDeleteEnvironment(environment) { + if (this.environment.id === environment.id) { + this.isLoading = true; + } + }, + }, +}; +</script> +<template> + <loading-button + v-gl-tooltip + :loading="isLoading" + :title="title" + :aria-label="title" + container-class="btn btn-danger d-none d-sm-none d-md-block" + data-toggle="modal" + data-target="#delete-environment-modal" + @click="onClick" + > + <icon name="remove" /> + </loading-button> +</template> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index dc489c804e9..ec5b1092c14 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -15,8 +15,9 @@ import ActionsComponent from './environment_actions.vue'; import ExternalUrlComponent from './environment_external_url.vue'; import MonitoringButtonComponent from './environment_monitoring.vue'; import PinComponent from './environment_pin.vue'; -import RollbackComponent from './environment_rollback.vue'; +import DeleteComponent from './environment_delete.vue'; import StopComponent from './environment_stop.vue'; +import RollbackComponent from './environment_rollback.vue'; import TerminalButtonComponent from './environment_terminal_button.vue'; /** @@ -33,6 +34,7 @@ export default { Icon, MonitoringButtonComponent, PinComponent, + DeleteComponent, RollbackComponent, StopComponent, TerminalButtonComponent, @@ -113,6 +115,15 @@ export default { }, /** + * Returns whether the environment can be deleted. + * + * @returns {Boolean} + */ + canDeleteEnvironment() { + return Boolean(this.model && this.model.can_delete && this.model.delete_path); + }, + + /** * Verifies if the `deployable` key is present in `last_deployment` key. * Used to verify whether we should or not render the rollback partial. * @@ -485,6 +496,7 @@ export default { this.externalURL || this.monitoringUrl || this.canStopEnvironment || + this.canDeleteEnvironment || this.canRetry ); }, @@ -680,6 +692,8 @@ export default { /> <stop-component v-if="canStopEnvironment" :environment="model" /> + + <delete-component v-if="canDeleteEnvironment" :environment="model" /> </div> </div> </div> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index 07b8d20fde0..cc1d86d06ed 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -9,6 +9,7 @@ import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '~/vue_shared/mixins/ci_pagination_api_mixin'; import EnableReviewAppButton from './enable_review_app_button.vue'; import StopEnvironmentModal from './stop_environment_modal.vue'; +import DeleteEnvironmentModal from './delete_environment_modal.vue'; import ConfirmRollbackModal from './confirm_rollback_modal.vue'; export default { @@ -18,6 +19,7 @@ export default { EnableReviewAppButton, GlButton, StopEnvironmentModal, + DeleteEnvironmentModal, }, mixins: [CIPaginationMixin, environmentsMixin, envrionmentsAppMixin], @@ -95,6 +97,7 @@ export default { <template> <div> <stop-environment-modal :environment="environmentInStopModal" /> + <delete-environment-modal :environment="environmentInDeleteModal" /> <confirm-rollback-modal :environment="environmentInRollbackModal" /> <div class="top-area"> diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue index 3caf723442e..d3e8fb7ff08 100644 --- a/app/assets/javascripts/environments/components/stop_environment_modal.vue +++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue @@ -63,10 +63,9 @@ export default { <template slot="header"> <h4 class="modal-title d-flex mw-100"> Stopping - <span v-gl-tooltip :title="environment.name" class="text-truncate ml-1 mr-1 flex-fill">{{ - environment.name - }}</span> - ? + <span v-gl-tooltip :title="environment.name" class="text-truncate ml-1 mr-1 flex-fill"> + {{ environment.name }}? + </span> </h4> </template> diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index d60c2efd618..30b02585692 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -3,10 +3,12 @@ import folderMixin from 'ee_else_ce/environments/mixins/environments_folder_view import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import StopEnvironmentModal from '../components/stop_environment_modal.vue'; +import DeleteEnvironmentModal from '../components/delete_environment_modal.vue'; export default { components: { StopEnvironmentModal, + DeleteEnvironmentModal, }, mixins: [environmentsMixin, CIPaginationMixin, folderMixin], @@ -39,6 +41,7 @@ export default { <template> <div :class="cssContainerClass"> <stop-environment-modal :environment="environmentInStopModal" /> + <delete-environment-modal :environment="environmentInDeleteModal" /> <h4 class="js-folder-name environments-folder-name"> {{ s__('Environments|Environments') }} / diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 1c5884b541c..4fadecdd3e9 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -27,6 +27,10 @@ export default { data() { const store = new EnvironmentsStore(); + const isDetailView = document.body.contains( + document.getElementById('environments-detail-view'), + ); + return { store, state: store.state, @@ -36,7 +40,9 @@ export default { page: getParameterByName('page') || '1', requestData: {}, environmentInStopModal: {}, + environmentInDeleteModal: {}, environmentInRollbackModal: {}, + isDetailView, }; }, @@ -121,6 +127,10 @@ export default { this.environmentInStopModal = environment; }, + updateDeleteModal(environment) { + this.environmentInDeleteModal = environment; + }, + updateRollbackModal(environment) { this.environmentInRollbackModal = environment; }, @@ -133,6 +143,30 @@ export default { this.postAction({ endpoint, errorMessage }); }, + deleteEnvironment(environment) { + const endpoint = environment.delete_path; + const mountedToShow = environment.mounted_to_show; + 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 (!mountedToShow) { + // 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(() => { + Flash(errorMessage); + }); + }, + rollbackEnvironment(environment) { const { retryUrl, isLastDeployment } = environment; const errorMessage = isLastDeployment @@ -178,36 +212,42 @@ export default { this.service = new EnvironmentsService(this.endpoint); this.requestData = { page: this.page, scope: this.scope, nested: true }; - 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(); - } + 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; + }, + }); - Visibility.change(() => { if (!Visibility.hidden()) { - this.poll.restart(); + this.isLoading = true; + this.poll.makeRequest(); } else { - this.poll.stop(); + 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); @@ -216,9 +256,13 @@ export default { 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); diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js new file mode 100644 index 00000000000..1929ed080a1 --- /dev/null +++ b/app/assets/javascripts/environments/mount_show.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import DeleteEnvironmentModal from './components/delete_environment_modal.vue'; +import environmentsMixin from './mixins/environments_mixin'; + +export default () => { + const el = document.getElementById('delete-environment-modal'); + const container = document.getElementById('environments-detail-view'); + + return new Vue({ + el, + components: { + DeleteEnvironmentModal, + }, + mixins: [environmentsMixin], + data() { + const environment = JSON.parse(JSON.stringify(container.dataset)); + environment.delete_path = environment.deletePath; + environment.mounted_to_show = true; + + return { + environment, + }; + }, + render(createElement) { + return createElement('delete-environment-modal', { + props: { + environment: this.environment, + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/environments/services/environments_service.js b/app/assets/javascripts/environments/services/environments_service.js index cb4ff6856db..122c8f84a2c 100644 --- a/app/assets/javascripts/environments/services/environments_service.js +++ b/app/assets/javascripts/environments/services/environments_service.js @@ -16,6 +16,11 @@ export default class EnvironmentsService { return axios.post(endpoint, {}); } + // eslint-disable-next-line class-methods-use-this + deleteAction(endpoint) { + return axios.delete(endpoint, {}); + } + getFolderContent(folderUrl) { return axios.get(`${folderUrl}.json?per_page=${this.folderResults}`); } diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index abecfba5718..9b0ee40a30a 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -9,6 +9,7 @@ import { getLocationHash } from './url_utility'; import { convertToCamelCase, convertToSnakeCase } from './text_utility'; import { isObject } from './type_utility'; import { isFunction } from 'lodash'; +import Cookies from 'js-cookie'; export const getPagePath = (index = 0) => { const page = $('body').attr('data-page') || ''; @@ -902,3 +903,10 @@ window.gl.utils = { spriteIcon, imagePath, }; + +// Methods to set and get Cookie +export const setCookie = (name, value) => Cookies.set(name, value, { expires: 365 }); + +export const getCookie = name => Cookies.get(name); + +export const removeCookie = name => Cookies.remove(name); diff --git a/app/assets/javascripts/pages/projects/environments/show/index.js b/app/assets/javascripts/pages/projects/environments/show/index.js new file mode 100644 index 00000000000..10e3e28f024 --- /dev/null +++ b/app/assets/javascripts/pages/projects/environments/show/index.js @@ -0,0 +1,3 @@ +import initShowEnvironment from '~/environments/mount_show'; + +document.addEventListener('DOMContentLoaded', () => initShowEnvironment()); diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb index 164cd5b9384..a9d1dc0759d 100644 --- a/app/controllers/projects/settings/operations_controller.rb +++ b/app/controllers/projects/settings/operations_controller.rb @@ -4,6 +4,9 @@ module Projects module Settings class OperationsController < Projects::ApplicationController before_action :authorize_admin_operations! + before_action :authorize_read_prometheus_alerts!, only: [:reset_alerting_token] + + respond_to :json, only: [:reset_alerting_token] helper_method :error_tracking_setting @@ -27,8 +30,24 @@ module Projects end end + def reset_alerting_token + result = ::Projects::Operations::UpdateService + .new(project, current_user, alerting_params) + .execute + + if result[:status] == :success + render json: { token: project.alerting_setting.token } + else + render json: {}, status: :unprocessable_entity + end + end + private + def alerting_params + { alerting_setting_attributes: { regenerate_token: true } } + end + def prometheus_service project.find_or_initialize_service(::PrometheusService.to_param) end diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index 6bf920448a5..68d78959407 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -50,4 +50,8 @@ module EnvironmentsHelper "cluster-applications-documentation-path" => help_page_path('user/clusters/applications.md', anchor: 'elastic-stack') } end + + def can_destroy_environment?(environment) + can?(current_user, :destroy_environment, environment) + end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 1fb0b83b010..4474534045b 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -4,6 +4,7 @@ module GitlabRoutingHelper extend ActiveSupport::Concern + include API::Helpers::RelatedResourcesHelpers included do Gitlab::Routing.includes_helpers(self) end @@ -29,6 +30,10 @@ module GitlabRoutingHelper metrics_project_environment_path(environment.project, environment, *args) end + def environment_delete_path(environment, *args) + expose_path(api_v4_projects_environments_path(id: environment.project.id, environment_id: environment.id)) + end + def issue_path(entity, *args) project_issue_path(entity.project, entity, *args) end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 35b727720ba..03260b28335 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -62,13 +62,16 @@ class CommitStatus < ApplicationRecord preload(project: :namespace) end - scope :match_id_and_lock_version, -> (slice) do + scope :match_id_and_lock_version, -> (items) do # it expects that items are an array of attributes to match # each hash needs to have `id` and `lock_version` - slice.inject(self) do |relation, item| - match = CommitStatus.where(item.slice(:id, :lock_version)) + or_conditions = items.inject(none) do |relation, item| + match = CommitStatus.default_scoped.where(item.slice(:id, :lock_version)) + relation.or(match) end + + merge(or_conditions) end # We use `CommitStatusEnums.failure_reasons` here so that EE can more easily diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb index 19f2daa1b01..a7f1fb66a88 100644 --- a/app/models/concerns/noteable.rb +++ b/app/models/concerns/noteable.rb @@ -79,6 +79,12 @@ module Noteable .discussions(self) end + def discussion_ids_relation + notes.select(:discussion_id) + .group(:discussion_id) + .order('MIN(created_at), MIN(id)') + end + def capped_notes_count(max) notes.limit(max).count end diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 75dfad4f3df..fd4ee069041 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -81,7 +81,7 @@ class PrometheusService < MonitoringService def prometheus_client return unless should_return_client? - Gitlab::PrometheusClient.new(api_url) + Gitlab::PrometheusClient.new(api_url, allow_local_requests: allow_local_api_url?) end def prometheus_available? @@ -94,7 +94,8 @@ class PrometheusService < MonitoringService end def allow_local_api_url? - self_monitoring_project? && internal_prometheus_url? + allow_local_requests_from_web_hooks_and_services? || + (self_monitoring_project? && internal_prometheus_url?) end def configured? @@ -111,6 +112,10 @@ class PrometheusService < MonitoringService api_url.present? && api_url == ::Gitlab::Prometheus::Internal.uri end + def allow_local_requests_from_web_hooks_and_services? + current_settings.allow_local_requests_from_web_hooks_and_services? + end + def should_return_client? api_url.present? && manual_configuration? && active? && valid? end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index b6127baca90..c7b5d7c8278 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -19,8 +19,6 @@ class Snippet < ApplicationRecord MAX_FILE_COUNT = 1 - ignore_column :repository_storage, remove_with: '12.10', remove_after: '2020-03-22' - cache_markdown_field :title, pipeline: :single_line cache_markdown_field :description cache_markdown_field :content diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb index be512dd3b94..f0187a39687 100644 --- a/app/policies/environment_policy.rb +++ b/app/policies/environment_policy.rb @@ -12,7 +12,13 @@ class EnvironmentPolicy < BasePolicy !@subject.stop_action_available? && can?(:update_environment, @subject) end + condition(:stopped) do + @subject.stopped? + end + rule { stop_with_deployment_allowed | stop_with_update_allowed }.enable :stop_environment + + rule { ~stopped }.prevent(:destroy_environment) end EnvironmentPolicy.prepend_if_ee('EE::EnvironmentPolicy') diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index aecefcc89ab..99aeca17699 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -271,6 +271,7 @@ class ProjectPolicy < BasePolicy enable :destroy_container_image enable :create_environment enable :update_environment + enable :destroy_environment enable :create_deployment enable :update_deployment enable :create_release @@ -316,6 +317,7 @@ class ProjectPolicy < BasePolicy enable :create_deploy_token enable :read_pod_logs enable :destroy_deploy_token + enable :read_prometheus_alerts end rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index d9af7af8a8b..7da5910a75b 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -28,6 +28,10 @@ class EnvironmentEntity < Grape::Entity cancel_auto_stop_project_environment_path(environment.project, environment) end + expose :delete_path do |environment| + environment_delete_path(environment) + end + expose :cluster_type, if: ->(environment, _) { cluster_platform_kubernetes? } do |environment| cluster.cluster_type end @@ -63,6 +67,10 @@ class EnvironmentEntity < Grape::Entity environment.elastic_stack_available? end + expose :can_delete do |environment| + can?(current_user, :destroy_environment, environment) + end + private alias_method :environment, :object diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb index 27bbf5c6e57..c06f572b52f 100644 --- a/app/services/projects/operations/update_service.rb +++ b/app/services/projects/operations/update_service.rb @@ -13,12 +13,30 @@ module Projects def project_update_params error_tracking_params + .merge(alerting_setting_params) .merge(metrics_setting_params) .merge(grafana_integration_params) .merge(prometheus_integration_params) .merge(incident_management_setting_params) end + def alerting_setting_params + return {} unless can?(current_user, :read_prometheus_alerts, project) + + attr = params[:alerting_setting_attributes] + return {} unless attr + + regenerate_token = attr.delete(:regenerate_token) + + if regenerate_token + attr[:token] = nil + else + attr = attr.except(:token) + end + + { alerting_setting_attributes: attr } + end + def metrics_setting_params attribs = params[:metrics_setting_attributes] return {} unless attribs diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 51b0b2722d1..b67f9d0cd08 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -23,7 +23,7 @@ .js-suggest-gitlab-ci-yml{ data: { toggle: 'popover', target: '#gitlab-ci-yml-selector', track_label: 'suggest_gitlab_ci_yml', - dismiss_key: "suggest_gitlab_ci_yml_#{@project.id}", + dismiss_key: @project.id, human_access: human_access } } .file-buttons diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 1afbe1fe24e..8f166e9aa16 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -17,5 +17,5 @@ .js-suggest-gitlab-ci-yml-commit-changes{ data: { toggle: 'popover', target: '#commit-changes', track_label: 'suggest_commit_first_project_gitlab_ci_yml', - dismiss_key: "suggest_commit_first_project_gitlab_ci_yml_#{@project.id}", + dismiss_key: @project.id, human_access: human_access } } diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index ff78abfddf4..3a7a93dc4e6 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -5,74 +5,81 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag 'page_bundles/xterm' -- if @environment.available? && can?(current_user, :stop_environment, @environment) - #stop-environment-modal.modal.fade{ tabindex: -1 } - .modal-dialog - .modal-content - .modal-header - %h4.modal-title.d-flex.mw-100 - = s_("Environments|Stopping") - %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } } - = @environment.name - ? - .modal-body - %p= s_('Environments|Are you sure you want to stop this environment?') - - unless @environment.stop_action_available? - .warning_message - %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, - emphasis_end: '</strong>'.html_safe, - ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, - ci_config_link_end: '</a>'.html_safe } - %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', - target: '_blank', - rel: 'noopener noreferrer' } - = s_('Environments|Learn more about stopping environments') - .modal-footer - = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } - = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do - = s_('Environments|Stop environment') +#environments-detail-view{ data: { name: @environment.name, id: @environment.id, delete_path: environment_delete_path(@environment)} } + - if @environment.available? && can?(current_user, :stop_environment, @environment) + #stop-environment-modal.modal.fade{ tabindex: -1 } + .modal-dialog + .modal-content + .modal-header + %h4.modal-title.d-flex.mw-100 + = s_("Environments|Stopping") + %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } } + #{@environment.name}? + .modal-body + %p= s_('Environments|Are you sure you want to stop this environment?') + - unless @environment.stop_action_available? + .warning_message + %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, + emphasis_end: '</strong>'.html_safe, + ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, + ci_config_link_end: '</a>'.html_safe } + %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', + target: '_blank', + rel: 'noopener noreferrer' } + = s_('Environments|Learn more about stopping environments') + .modal-footer + = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } + = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do + = s_('Environments|Stop environment') -.top-area.justify-content-between - .d-flex - %h3.page-title= @environment.name - - if @environment.auto_stop_at? - %p.align-self-end.prepend-left-8 - = s_('Environments|Auto stops %{auto_stop_time}').html_safe % {auto_stop_time: time_ago_with_tooltip(@environment.auto_stop_at)} - .nav-controls.my-2 - = render 'projects/environments/pin_button', environment: @environment - = render 'projects/environments/terminal_button', environment: @environment - = render 'projects/environments/external_url', environment: @environment - = render 'projects/environments/metrics_button', environment: @environment - - if can?(current_user, :update_environment, @environment) - = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn' - - if @environment.available? && can?(current_user, :stop_environment, @environment) - = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', - target: '#stop-environment-modal' } do - = sprite_icon('stop') - = s_('Environments|Stop') + - if can_destroy_environment?(@environment) + #delete-environment-modal -.environments-container - - if @deployments.blank? - .empty-state - .text-content - %h4.state-title - = _("You don't have any deployments right now.") - %p.blank-state-text - = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe - .text-center - = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" - - else - .table-holder - .ci-table.environments{ role: 'grid' } - .gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-15{ role: 'columnheader' }= _('Status') - .table-section.section-10{ role: 'columnheader' }= _('ID') - .table-section.section-10{ role: 'columnheader' }= _('Triggerer') - .table-section.section-25{ role: 'columnheader' }= _('Commit') - .table-section.section-10{ role: 'columnheader' }= _('Job') - .table-section.section-10{ role: 'columnheader' }= _('Created') - .table-section.section-10{ role: 'columnheader' }= _('Deployed') + .top-area.justify-content-between + .d-flex + %h3.page-title= @environment.name + - if @environment.auto_stop_at? + %p.align-self-end.prepend-left-8 + = s_('Environments|Auto stops %{auto_stop_time}').html_safe % {auto_stop_time: time_ago_with_tooltip(@environment.auto_stop_at)} + .nav-controls.my-2 + = render 'projects/environments/pin_button', environment: @environment + = render 'projects/environments/terminal_button', environment: @environment + = render 'projects/environments/external_url', environment: @environment + = render 'projects/environments/metrics_button', environment: @environment + - if can?(current_user, :update_environment, @environment) + = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn' + - if @environment.available? && can?(current_user, :stop_environment, @environment) + = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', + target: '#stop-environment-modal' } do + = sprite_icon('stop') + = s_('Environments|Stop') + - if can_destroy_environment?(@environment) + = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', + target: '#delete-environment-modal' } do + = s_('Environments|Delete') - = render @deployments + .environments-container + - if @deployments.blank? + .empty-state + .text-content + %h4.state-title + = _("You don't have any deployments right now.") + %p.blank-state-text + = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe + .text-center + = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" + - else + .table-holder + .ci-table.environments{ role: 'grid' } + .gl-responsive-table-row.table-row-header{ role: 'row' } + .table-section.section-15{ role: 'columnheader' }= _('Status') + .table-section.section-10{ role: 'columnheader' }= _('ID') + .table-section.section-10{ role: 'columnheader' }= _('Triggerer') + .table-section.section-25{ role: 'columnheader' }= _('Commit') + .table-section.section-10{ role: 'columnheader' }= _('Job') + .table-section.section-10{ role: 'columnheader' }= _('Created') + .table-section.section-10{ role: 'columnheader' }= _('Deployed') - = paginate @deployments, theme: 'gitlab' + = render @deployments + + = paginate @deployments, theme: 'gitlab' diff --git a/app/views/shared/icons/_dev_ops_score_no_data.svg b/app/views/shared/icons/_dev_ops_score_no_data.svg index ed32b2333e7..5de929859ae 100644 --- a/app/views/shared/icons/_dev_ops_score_no_data.svg +++ b/app/views/shared/icons/_dev_ops_score_no_data.svg @@ -34,7 +34,6 @@ <rect width="38" height="4" y="12" fill="#FB722E" rx="2"/> </g> <path fill="#EEE" d="M4 14h106v4H4z"/> - <path fill="#333" d="M35.724 138h9.696v-2.856h-2.856V122.76h-2.592c-1.08.648-2.136 1.08-3.792 1.392v2.184h2.856v8.808h-3.312V138zm17.736.288c-2.952 0-5.76-2.208-5.76-7.56 0-5.688 2.952-8.256 6.168-8.256 2.016 0 3.48.84 4.44 1.824l-1.848 2.112c-.528-.576-1.488-1.08-2.376-1.08-1.68 0-3.024 1.2-3.144 4.752.792-1.008 2.112-1.608 3.048-1.608 2.616 0 4.536 1.488 4.536 4.704 0 3.168-2.304 5.112-5.064 5.112zm-.072-2.64c1.056 0 1.92-.744 1.92-2.472 0-1.608-.84-2.208-1.992-2.208-.792 0-1.68.432-2.304 1.512.312 2.4 1.32 3.168 2.376 3.168zM63.9 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/> </g> </g> </svg> diff --git a/app/views/shared/icons/_dev_ops_score_no_index.svg b/app/views/shared/icons/_dev_ops_score_no_index.svg index 95c00e81d10..0577efca93f 100644 --- a/app/views/shared/icons/_dev_ops_score_no_index.svg +++ b/app/views/shared/icons/_dev_ops_score_no_index.svg @@ -17,7 +17,6 @@ <rect width="38" height="4" y="12" fill="#FB722E" rx="2"/> </g> <path fill="#EEE" d="M2 12h106v4H2z"/> - <path fill="#333" d="M38.048 127.792c.792 0 1.68-.432 2.28-1.512-.312-2.4-1.296-3.168-2.376-3.168-1.032 0-1.92.744-1.92 2.472 0 1.608.864 2.208 2.016 2.208zm-.552 8.496c-2.016 0-3.504-.864-4.464-1.824l1.872-2.112c.504.576 1.464 1.08 2.352 1.08 1.704 0 3.024-1.2 3.144-4.752-.792 1.008-2.112 1.608-3.048 1.608-2.592 0-4.536-1.488-4.536-4.704 0-3.168 2.304-5.112 5.064-5.112 2.952 0 5.784 2.208 5.784 7.56 0 5.688-2.976 8.256-6.168 8.256zm13.488 0c-3.048 0-5.304-1.704-5.304-4.176 0-1.848 1.152-2.976 2.592-3.744v-.096c-1.176-.888-2.04-1.992-2.04-3.6 0-2.592 2.04-4.2 4.872-4.2 2.784 0 4.632 1.656 4.632 4.176 0 1.464-.936 2.64-1.992 3.336v.096c1.464.792 2.64 1.968 2.64 3.984 0 2.4-2.16 4.224-5.4 4.224zm.96-9.168c.6-.696.936-1.44.936-2.232 0-1.176-.696-1.968-1.848-1.968-.936 0-1.704.576-1.704 1.752 0 1.248 1.056 1.848 2.616 2.448zm-.888 6.72c1.176 0 2.04-.624 2.04-1.896 0-1.344-1.296-1.848-3.216-2.664-.672.624-1.176 1.488-1.176 2.424 0 1.344 1.08 2.136 2.352 2.136zm10.8-3.84c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/> </g> <g transform="translate(122)"> <rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/> @@ -39,7 +38,6 @@ <rect width="8" height="4" x="73" y="14" fill="#EEE" rx="2"/> <rect width="8" height="4" x="86" y="14" fill="#EEE" rx="2"/> <rect width="8" height="4" x="99" y="14" fill="#EEE" rx="2"/> - <path fill="#EEE" d="M46.716 138.288c-3.264 0-5.448-2.784-5.448-7.968s2.184-7.848 5.448-7.848c3.264 0 5.448 2.664 5.448 7.848 0 5.184-2.184 7.968-5.448 7.968zm0-2.736c1.2 0 2.112-1.08 2.112-5.232 0-4.176-.912-5.112-2.112-5.112-1.176 0-2.112.936-2.112 5.112 0 4.152.936 5.232 2.112 5.232zM57.564 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/> </g> <g transform="translate(243)"> <rect width="110" height="168" x="2" y="2" fill="#FFF" rx="10"/> @@ -61,7 +59,6 @@ <rect width="8" height="4" x="73" y="14" fill="#EEE" rx="2"/> <rect width="8" height="4" x="86" y="14" fill="#EEE" rx="2"/> <rect width="8" height="4" x="99" y="14" fill="#EEE" rx="2"/> - <path fill="#EEE" d="M46.716 138.288c-3.264 0-5.448-2.784-5.448-7.968s2.184-7.848 5.448-7.848c3.264 0 5.448 2.664 5.448 7.848 0 5.184-2.184 7.968-5.448 7.968zm0-2.736c1.2 0 2.112-1.08 2.112-5.232 0-4.176-.912-5.112-2.112-5.112-1.176 0-2.112.936-2.112 5.112 0 4.152.936 5.232 2.112 5.232zM57.564 132c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024zm.528 8.256l8.448-16.224h2.04l-8.448 16.224h-2.04zm11.016 0c-2.256 0-3.888-1.848-3.888-4.992 0-3.12 1.632-4.944 3.888-4.944 2.256 0 3.912 1.824 3.912 4.944 0 3.144-1.656 4.992-3.912 4.992zm0-1.968c.792 0 1.44-.792 1.44-3.024s-.648-2.976-1.44-2.976c-.792 0-1.44.744-1.44 2.976s.648 3.024 1.44 3.024z"/> </g> </g> </svg> |