diff options
author | John T Skarbek <jtslear@gmail.com> | 2019-03-05 23:06:19 +0300 |
---|---|---|
committer | John T Skarbek <jtslear@gmail.com> | 2019-03-05 23:06:19 +0300 |
commit | f027f64e51b7077665f0a5fff5ffb6ae6c87f24b (patch) | |
tree | 3b5ce554a94337072c0ff2ef990f1fcb817b0ea4 | |
parent | 098215f3b306ea1c606a12cc03fc38caa02d9954 (diff) | |
parent | a592a78072bb44fed1a25c25f2cabdc4cf4bc0bd (diff) |
Merge remote-tracking branch 'origin/master' into 11-9-stable
40 files changed, 669 insertions, 50 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js index 9482a9f166d..58cf057b2c2 100644 --- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js @@ -173,7 +173,9 @@ export class CopyAsGFM { wrapEl.appendChild(node.cloneNode(true)); const doc = DOMParser.fromSchema(schema.default).parse(wrapEl); - const res = markdownSerializer.default.serialize(doc); + const res = markdownSerializer.default.serialize(doc, { + tightLists: true, + }); return res; }) .catch(() => {}); diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue new file mode 100644 index 00000000000..a8ee3f4ac10 --- /dev/null +++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue @@ -0,0 +1,108 @@ +<script> +/** + * Render modal to confirm rollback/redeploy. + */ + +import _ from 'underscore'; +import { GlModal } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; + +import eventHub from '../event_hub'; + +export default { + name: 'ConfirmRollbackModal', + + components: { + GlModal, + }, + + props: { + environment: { + type: Object, + required: true, + }, + }, + + computed: { + modalTitle() { + const title = this.environment.isLastDeployment + ? s__('Environments|Re-deploy environment %{name}?') + : s__('Environments|Rollback environment %{name}?'); + + return sprintf(title, { + name: _.escape(this.environment.name), + }); + }, + + commitShortSha() { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'short_id'); + }, + + commitUrl() { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'commit_path'); + }, + + commitTitle() { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'title'); + }, + + modalText() { + const linkStart = `<a class="commit-sha" href="${_.escape(this.commitUrl)}">`; + const commitId = _.escape(this.commitShortSha); + const linkEnd = '</a>'; + const name = _.escape(this.name); + const body = this.environment.isLastDeployment + ? s__( + 'Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?', + ) + : s__( + 'Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?', + ); + return sprintf( + body, + { + commitId, + linkStart, + linkEnd, + name, + }, + false, + ); + }, + + modalActionText() { + return this.environment.isLastDeployment + ? s__('Environments|Re-deploy') + : s__('Environments|Rollback'); + }, + }, + + methods: { + onOk() { + eventHub.$emit('rollbackEnvironment', this.environment); + }, + + commitData(lastDeployment, key) { + if (lastDeployment && lastDeployment.commit) { + return lastDeployment.commit[key]; + } + + return ''; + }, + }, +}; +</script> +<template> + <gl-modal + :title="modalTitle" + modal-id="confirm-rollback-modal" + :ok-title="modalActionText" + ok-variant="danger" + @ok="onOk" + > + <p v-html="modalText"></p> + </gl-modal> +</template> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 503c1b38f71..eb817beab9d 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -556,6 +556,7 @@ export default { <rollback-component v-if="canRetry" + :environment="model" :is-last-deployment="isLastDeployment" :retry-url="retryUrl" /> diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 50c86af057c..266cdc42518 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -5,29 +5,38 @@ * * Makes a post request when the button is clicked. */ +import { GlTooltipDirective, GlLoadingIcon, GlModalDirective, GlButton } from '@gitlab/ui'; import { s__ } from '~/locale'; -import { GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; +import ConfirmRollbackModal from './confirm_rollback_modal.vue'; import eventHub from '../event_hub'; export default { components: { Icon, GlLoadingIcon, + GlButton, + ConfirmRollbackModal, }, directives: { GlTooltip: GlTooltipDirective, + GlModal: GlModalDirective, }, props: { - retryUrl: { - type: String, - default: '', - }, - isLastDeployment: { type: Boolean, default: true, }, + + environment: { + type: Object, + required: true, + }, + + retryUrl: { + type: String, + required: true, + }, }, data() { return { @@ -45,23 +54,31 @@ export default { methods: { onClick() { - this.isLoading = true; - - eventHub.$emit('postAction', { endpoint: this.retryUrl }); + eventHub.$emit('requestRollbackEnvironment', { + ...this.environment, + retryUrl: this.retryUrl, + isLastDeployment: this.isLastDeployment, + }); + eventHub.$on('rollbackEnvironment', environment => { + if (environment.id === this.environment.id) { + this.isLoading = true; + } + }); }, }, }; </script> <template> - <button + <gl-button v-gl-tooltip + v-gl-modal.confirm-rollback-modal + variant="secondary" :disabled="isLoading" :title="title" - type="button" - class="btn d-none d-sm-none d-md-block" + class="d-none d-md-block" @click="onClick" > <icon v-if="isLastDeployment" name="repeat" /> <icon v-else name="redo" /> <gl-loading-icon v-if="isLoading" /> - </button> + </gl-button> </template> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index aa2417d3194..6e55c3f901a 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -6,11 +6,13 @@ import eventHub from '../event_hub'; import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import StopEnvironmentModal from './stop_environment_modal.vue'; +import ConfirmRollbackModal from './confirm_rollback_modal.vue'; export default { components: { emptyState, StopEnvironmentModal, + ConfirmRollbackModal, }, mixins: [CIPaginationMixin, environmentsMixin], @@ -87,6 +89,7 @@ export default { <template> <div :class="cssContainerClass"> <stop-environment-modal :environment="environmentInStopModal" /> + <confirm-rollback-modal :environment="environmentInRollbackModal" /> <div class="top-area"> <tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" /> diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 9d83840c87c..71b6b578196 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -36,6 +36,7 @@ export default { page: getParameterByName('page') || '1', requestData: {}, environmentInStopModal: {}, + environmentInRollbackModal: {}, }; }, @@ -116,6 +117,10 @@ export default { this.environmentInStopModal = environment; }, + updateRollbackModal(environment) { + this.environmentInRollbackModal = environment; + }, + stopEnvironment(environment) { const endpoint = environment.stop_path; const errorMessage = s__( @@ -123,6 +128,16 @@ export default { ); this.postAction({ endpoint, 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 }); + }, }, computed: { @@ -181,11 +196,17 @@ export default { eventHub.$on('postAction', this.postAction); eventHub.$on('requestStopEnvironment', this.updateStopModal); eventHub.$on('stopEnvironment', this.stopEnvironment); + + eventHub.$on('requestRollbackEnvironment', this.updateRollbackModal); + eventHub.$on('rollbackEnvironment', this.rollbackEnvironment); }, beforeDestroy() { eventHub.$off('postAction', this.postAction); eventHub.$off('requestStopEnvironment', this.updateStopModal); eventHub.$off('stopEnvironment', this.stopEnvironment); + + eventHub.$off('requestRollbackEnvironment', this.updateRollbackModal); + eventHub.$off('rollbackEnvironment', this.rollbackEnvironment); }, }; diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue index 6981afe1ead..43ae54133af 100644 --- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue +++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue @@ -48,7 +48,7 @@ export default { } }, methods: { - ...mapActions(['startPolling']), + ...mapActions(['startPolling', 'restartPolling']), }, }; </script> @@ -56,19 +56,17 @@ export default { <template> <div> <div v-if="errorTrackingEnabled"> - <div v-if="loading" class="py-3"><gl-loading-icon :size="3" /></div> + <div v-if="loading" class="py-3"> + <gl-loading-icon :size="3" /> + </div> <div v-else> <div class="d-flex justify-content-end"> - <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank" - >View in Sentry <icon name="external-link" /> + <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank"> + {{ __('View in Sentry') }} + <icon name="external-link" /> </gl-button> </div> - <gl-table - :items="errors" - :fields="$options.fields" - :show-empty="true" - :empty-text="__('No errors to display')" - > + <gl-table :items="errors" :fields="$options.fields" :show-empty="true"> <template slot="HEAD_events" slot-scope="data"> <div class="text-right">{{ data.label }}</div> </template> @@ -102,6 +100,14 @@ export default { <time-ago :time="errors.item.lastSeen" class="text-secondary" /> </div> </template> + <template slot="empty"> + <div ref="empty"> + {{ __('No errors to display.') }} + <gl-link class="js-try-again" @click="restartPolling"> + {{ __('Check again') }} + </gl-link> + </div> + </template> </gl-table> </div> </div> diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js index 11aec312368..d42e4f145dc 100644 --- a/app/assets/javascripts/error_tracking/store/actions.js +++ b/app/assets/javascripts/error_tracking/store/actions.js @@ -6,7 +6,7 @@ import { __, sprintf } from '~/locale'; let eTagPoll; -export function startPolling({ commit }, endpoint) { +export function startPolling({ commit, dispatch }, endpoint) { eTagPoll = new Poll({ resource: Service, method: 'getErrorList', @@ -18,6 +18,7 @@ export function startPolling({ commit }, endpoint) { commit(types.SET_ERRORS, data.errors); commit(types.SET_EXTERNAL_URL, data.external_url); commit(types.SET_LOADING, false); + dispatch('stopPolling'); }, errorCallback: response => { let errorMessage = ''; @@ -36,4 +37,16 @@ export function startPolling({ commit }, endpoint) { eTagPoll.makeRequest(); } +export const stopPolling = () => { + if (eTagPoll) eTagPoll.stop(); +}; + +export function restartPolling({ commit }) { + commit(types.SET_ERRORS, []); + commit(types.SET_EXTERNAL_URL, ''); + commit(types.SET_LOADING, true); + + if (eTagPoll) eTagPoll.restart(); +} + export default () => {}; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index d0e33dfc853..aad5150c0b5 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -404,6 +404,7 @@ img.emoji { .flex-grow { flex-grow: 1; } .flex-no-shrink { flex-shrink: 0; } .ws-initial { white-space: initial; } +.ws-normal { white-space: normal; } .overflow-auto { overflow: auto; } .d-flex-center { diff --git a/app/graphql/resolvers/metadata_resolver.rb b/app/graphql/resolvers/metadata_resolver.rb new file mode 100644 index 00000000000..3a79e6434fb --- /dev/null +++ b/app/graphql/resolvers/metadata_resolver.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Resolvers + class MetadataResolver < BaseResolver + type Types::MetadataType, null: false + + def resolve(**args) + { version: Gitlab::VERSION, revision: Gitlab.revision } + end + end +end diff --git a/app/graphql/types/metadata_type.rb b/app/graphql/types/metadata_type.rb new file mode 100644 index 00000000000..2d8bad0614b --- /dev/null +++ b/app/graphql/types/metadata_type.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Types + class MetadataType < ::Types::BaseObject + graphql_name 'Metadata' + + field :version, GraphQL::STRING_TYPE, null: false + field :revision, GraphQL::STRING_TYPE, null: false + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 954bcc0a5a3..472fe5d6ec2 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Types - class QueryType < BaseObject + class QueryType < ::Types::BaseObject graphql_name 'Query' field :project, Types::ProjectType, @@ -10,6 +10,14 @@ module Types description: "Find a project", authorize: :read_project + field :metadata, Types::MetadataType, + null: true, + resolver: Resolvers::MetadataResolver, + description: 'Metadata about GitLab' do |*args| + + authorize :read_instance_metadata + end + field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new end end diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 16c58730878..d412a591fdc 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -68,6 +68,10 @@ class GlobalPolicy < BasePolicy enable :read_users_list end + rule { ~anonymous }.policy do + enable :read_instance_metadata + end + rule { admin }.policy do enable :read_custom_attribute enable :update_custom_attribute diff --git a/app/views/projects/deployments/_confirm_rollback_modal.html.haml b/app/views/projects/deployments/_confirm_rollback_modal.html.haml new file mode 100644 index 00000000000..ff40e404e5f --- /dev/null +++ b/app/views/projects/deployments/_confirm_rollback_modal.html.haml @@ -0,0 +1,23 @@ +- commit_sha = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha has-tooltip", title: h(deployment.commit_title) +.modal.ws-normal.fade{ tabindex: -1, id: "confirm-rollback-modal-#{deployment.id}" } + .modal-dialog + .modal-content + .modal-header + %h4.modal-title.d-flex.mw-100 + - if deployment.last? + = s_("Environments|Re-deploy environment %{environment_name}?") % {environment_name: @environment.name} + - else + = s_("Environments|Rollback environment %{environment_name}?") % {environment_name: @environment.name} + .modal-body + - if deployment.last? + %p= s_('Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?').html_safe % {commit_id: commit_sha} + - else + %p + = s_('Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?').html_safe % {commit_id: commit_sha} + .modal-footer + = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } + = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-danger' do + - if deployment.last? + = s_('Environments|Re-deploy') + - else + = s_('Environments|Rollback') diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml index 1bd538a08ff..d6bf8d564de 100644 --- a/app/views/projects/deployments/_rollback.haml +++ b/app/views/projects/deployments/_rollback.haml @@ -1,7 +1,8 @@ - if can?(current_user, :create_deployment, deployment) - tooltip = deployment.last? ? s_('Environments|Re-deploy to environment') : s_('Environments|Rollback environment') - = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build has-tooltip', title: tooltip do + = button_tag class: 'btn btn-default btn-build has-tooltip', type: 'button', data: { toggle: 'modal', target: "#confirm-rollback-modal-#{deployment.id}" }, title: tooltip do - if deployment.last? = sprite_icon('repeat') - else = sprite_icon('redo') + = render 'projects/deployments/confirm_rollback_modal', deployment: deployment diff --git a/babel.config.js b/babel.config.js index e3db4dcbc9a..78d14095b0b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -41,6 +41,11 @@ if (BABEL_ENV === 'karma' || BABEL_ENV === 'coverage') { // Jest is running in node environment if (BABEL_ENV === 'jest') { plugins.push('@babel/plugin-transform-modules-commonjs'); + /* + without the following, babel-plugin-istanbul throws an error: + https://gitlab.com/gitlab-org/gitlab-ce/issues/58390 + */ + plugins.push('babel-plugin-dynamic-import-node'); } module.exports = { presets, plugins }; diff --git a/changelogs/unreleased/27333-re-deploy-rollback-button-should-ask-for-confirmation-before-executing.yml b/changelogs/unreleased/27333-re-deploy-rollback-button-should-ask-for-confirmation-before-executing.yml new file mode 100644 index 00000000000..8c5f05c3575 --- /dev/null +++ b/changelogs/unreleased/27333-re-deploy-rollback-button-should-ask-for-confirmation-before-executing.yml @@ -0,0 +1,5 @@ +--- +title: Add Confirmation Modal to Rollback on Environment +merge_request: 25110 +author: +type: added diff --git a/changelogs/unreleased/32714-copying-comment-with-ordered-list-includes-extraneous-newlines.yml b/changelogs/unreleased/32714-copying-comment-with-ordered-list-includes-extraneous-newlines.yml new file mode 100644 index 00000000000..d0bb4225ce4 --- /dev/null +++ b/changelogs/unreleased/32714-copying-comment-with-ordered-list-includes-extraneous-newlines.yml @@ -0,0 +1,5 @@ +--- +title: Fixed "Copying comment with ordered list includes extraneous newlines" +merge_request: 25695 +author: +type: fixed diff --git a/changelogs/unreleased/56809-graphql-version-api.yml b/changelogs/unreleased/56809-graphql-version-api.yml new file mode 100644 index 00000000000..72a4b4e5819 --- /dev/null +++ b/changelogs/unreleased/56809-graphql-version-api.yml @@ -0,0 +1,5 @@ +--- +title: Add metadata about the GitLab server to GraphQL +merge_request: 24636 +author: +type: added diff --git a/changelogs/unreleased/56851-error-tracking-page-seems-broken.yml b/changelogs/unreleased/56851-error-tracking-page-seems-broken.yml new file mode 100644 index 00000000000..ff4aebb9381 --- /dev/null +++ b/changelogs/unreleased/56851-error-tracking-page-seems-broken.yml @@ -0,0 +1,5 @@ +--- +title: Fix error tracking list page +merge_request: 24806 +author: +type: fixed diff --git a/doc/install/requirements.md b/doc/install/requirements.md index c1f2297f3be..8ab7189c2a6 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -51,6 +51,8 @@ Apart from a local hard drive you can also mount a volume that supports the netw If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab. +NOTE: **Note:** Since file system performance may affect GitLab's overall performance, we do not recommend using EFS for storage. See the [relevant documentation](../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details. + ### CPU - 1 core supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb new file mode 100644 index 00000000000..94010ab1bc2 --- /dev/null +++ b/lib/api/helpers/graphql_helpers.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module API + module Helpers + # GraphqlHelpers is used by the REST API when it is acting like a client + # against the graphql API. Helper code for the graphql server implementation + # should be in app/graphql/ or lib/gitlab/graphql/ + module GraphqlHelpers + def conditionally_graphql!(fallback:, query:, context: {}, transform: nil) + return fallback.call unless Feature.enabled?(:graphql) + + result = GitlabSchema.execute(query, context: context) + + if transform + transform.call(result) + else + result + end + end + end + end +end diff --git a/lib/api/version.rb b/lib/api/version.rb index 74cd857f447..eca1b529094 100644 --- a/lib/api/version.rb +++ b/lib/api/version.rb @@ -2,13 +2,29 @@ module API class Version < Grape::API + helpers ::API::Helpers::GraphqlHelpers + before { authenticate! } + METADATA_QUERY = <<~EOF + { + metadata { + version + revision + } + } + EOF + desc 'Get the version information of the GitLab instance.' do detail 'This feature was introduced in GitLab 8.13.' end get '/version' do - { version: Gitlab::VERSION, revision: Gitlab.revision } + conditionally_graphql!( + query: METADATA_QUERY, + context: { current_user: current_user }, + transform: ->(result) { result.dig('data', 'metadata') }, + fallback: -> { { version: Gitlab::VERSION, revision: Gitlab.revision } } + ) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3abd570175e..92784f2c49d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1389,6 +1389,9 @@ msgstr "" msgid "Chat" msgstr "" +msgid "Check again" +msgstr "" + msgid "Check the %{docs_link_start}documentation%{docs_link_end}." msgstr "" @@ -3052,6 +3055,12 @@ msgstr "" msgid "Environments|An error occurred while making the request." msgstr "" +msgid "Environments|An error occurred while re-deploying the environment, please try again" +msgstr "" + +msgid "Environments|An error occurred while rolling back the environment, please try again" +msgstr "" + msgid "Environments|An error occurred while stopping the environment, please try again" msgstr "" @@ -3097,15 +3106,33 @@ msgstr "" msgid "Environments|Open live environment" msgstr "" +msgid "Environments|Re-deploy" +msgstr "" + +msgid "Environments|Re-deploy environment %{environment_name}?" +msgstr "" + +msgid "Environments|Re-deploy environment %{name}?" +msgstr "" + msgid "Environments|Re-deploy to environment" msgstr "" msgid "Environments|Read more about environments" msgstr "" +msgid "Environments|Rollback" +msgstr "" + msgid "Environments|Rollback environment" msgstr "" +msgid "Environments|Rollback environment %{environment_name}?" +msgstr "" + +msgid "Environments|Rollback environment %{name}?" +msgstr "" + msgid "Environments|Show all" msgstr "" @@ -3118,6 +3145,18 @@ msgstr "" msgid "Environments|Stopping" msgstr "" +msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?" +msgstr "" + +msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?" +msgstr "" + +msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?" +msgstr "" + +msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?" +msgstr "" + msgid "Environments|Updated" msgstr "" @@ -5006,7 +5045,7 @@ msgstr "" msgid "No due date" msgstr "" -msgid "No errors to display" +msgid "No errors to display." msgstr "" msgid "No estimate or time spent" @@ -8336,6 +8375,9 @@ msgstr "" msgid "View group labels" msgstr "" +msgid "View in Sentry" +msgstr "" + msgid "View it on GitLab" msgstr "" diff --git a/package.json b/package.json index 86ee0de475e..cd121a5905d 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "@vue/test-utils": "^1.0.0-beta.25", "axios-mock-adapter": "^1.15.0", "babel-jest": "^24.1.0", + "babel-plugin-dynamic-import-node": "^2.2.0", "babel-plugin-istanbul": "^5.1.0", "babel-plugin-rewire": "^1.2.0", "chalk": "^2.4.1", diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index 60ddb02da2c..c30ac9c4ae2 100644 --- a/spec/features/markdown/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb @@ -55,15 +55,10 @@ describe 'Copy as GFM', :js do To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/). * Manage Git repositories with fine grained access controls that keep your code secure - * Perform code reviews and enhance collaboration with merge requests - * Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications - * Each project can also have an issue tracker, issue board, and a wiki - * Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises - * Completely free and open source (MIT Expat license) GFM ) @@ -116,13 +111,11 @@ describe 'Copy as GFM', :js do <<~GFM, * [ ] Unchecked task - * [x] Checked task GFM <<~GFM 1. [ ] Unchecked ordered task - 1. [x] Checked ordered task GFM ) @@ -551,7 +544,6 @@ describe 'Copy as GFM', :js do <<~GFM, * List item - * List item 2 GFM @@ -565,7 +557,6 @@ describe 'Copy as GFM', :js do # nested lists <<~GFM, * Nested - * Lists GFM @@ -578,7 +569,6 @@ describe 'Copy as GFM', :js do <<~GFM, 1. Ordered list item - 1. Ordered list item 2 GFM @@ -592,7 +582,6 @@ describe 'Copy as GFM', :js do # nested ordered list <<~GFM, 1. Nested - 1. Ordered lists GFM diff --git a/spec/graphql/resolvers/metadata_resolver_spec.rb b/spec/graphql/resolvers/metadata_resolver_spec.rb new file mode 100644 index 00000000000..e662ed127a5 --- /dev/null +++ b/spec/graphql/resolvers/metadata_resolver_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Resolvers::MetadataResolver do + include GraphqlHelpers + + describe '#resolve' do + it 'returns version and revision' do + expect(resolve(described_class)).to eq(version: Gitlab::VERSION, revision: Gitlab.revision) + end + end +end diff --git a/spec/graphql/types/metadata_type_spec.rb b/spec/graphql/types/metadata_type_spec.rb new file mode 100644 index 00000000000..55205bf5b6a --- /dev/null +++ b/spec/graphql/types/metadata_type_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe GitlabSchema.types['Metadata'] do + it { expect(described_class.graphql_name).to eq('Metadata') } +end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index e1df6f9811d..07c61ea7647 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -5,7 +5,7 @@ describe GitlabSchema.types['Query'] do expect(described_class.graphql_name).to eq('Query') end - it { is_expected.to have_graphql_fields(:project, :echo) } + it { is_expected.to have_graphql_fields(:project, :echo, :metadata) } describe 'project field' do subject { described_class.fields['project'] } @@ -20,4 +20,17 @@ describe GitlabSchema.types['Query'] do is_expected.to require_graphql_authorizations(:read_project) end end + + describe 'metadata field' do + subject { described_class.fields['metadata'] } + + it 'returns metadata' do + is_expected.to have_graphql_type(Types::MetadataType) + is_expected.to have_graphql_resolver(Resolvers::MetadataResolver) + end + + it 'authorizes with read_instance_metadata' do + is_expected.to require_graphql_authorizations(:read_instance_metadata) + end + end end diff --git a/spec/javascripts/behaviors/copy_as_gfm_spec.js b/spec/javascripts/behaviors/copy_as_gfm_spec.js index ca849f75860..d653fca0988 100644 --- a/spec/javascripts/behaviors/copy_as_gfm_spec.js +++ b/spec/javascripts/behaviors/copy_as_gfm_spec.js @@ -100,7 +100,7 @@ describe('CopyAsGFM', () => { simulateCopy(); setTimeout(() => { - const expectedGFM = '* List Item1\n\n* List Item2'; + const expectedGFM = '* List Item1\n* List Item2'; expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM); done(); @@ -114,7 +114,7 @@ describe('CopyAsGFM', () => { simulateCopy(); setTimeout(() => { - const expectedGFM = '1. List Item1\n\n1. List Item2'; + const expectedGFM = '1. List Item1\n1. List Item2'; expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM); done(); diff --git a/spec/javascripts/environments/confirm_rollback_modal_spec.js b/spec/javascripts/environments/confirm_rollback_modal_spec.js new file mode 100644 index 00000000000..05715bce38f --- /dev/null +++ b/spec/javascripts/environments/confirm_rollback_modal_spec.js @@ -0,0 +1,70 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlModal } from '@gitlab/ui'; +import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; +import eventHub from '~/environments/event_hub'; + +describe('Confirm Rollback Modal Component', () => { + let environment; + + beforeEach(() => { + environment = { + name: 'test', + last_deployment: { + commit: { + short_id: 'abc0123', + }, + }, + modalId: 'test', + }; + }); + + it('should show "Rollback" when isLastDeployment is false', () => { + const component = shallowMount(ConfirmRollbackModal, { + propsData: { + environment: { + ...environment, + isLastDeployment: false, + }, + }, + }); + const modal = component.find(GlModal); + + expect(modal.attributes('title')).toContain('Rollback'); + expect(modal.attributes('title')).toContain('test'); + expect(modal.attributes('ok-title')).toBe('Rollback'); + expect(modal.text()).toContain('commit abc0123'); + expect(modal.text()).toContain('Are you sure you want to continue?'); + }); + + it('should show "Re-deploy" when isLastDeployment is true', () => { + const component = shallowMount(ConfirmRollbackModal, { + propsData: { + environment: { + ...environment, + isLastDeployment: true, + }, + }, + }); + const modal = component.find(GlModal); + + expect(modal.attributes('title')).toContain('Re-deploy'); + expect(modal.attributes('title')).toContain('test'); + expect(modal.attributes('ok-title')).toBe('Re-deploy'); + expect(modal.text()).toContain('commit abc0123'); + expect(modal.text()).toContain('Are you sure you want to continue?'); + }); + + it('should emit the "rollback" event when "ok" is clicked', () => { + environment = { ...environment, isLastDeployment: true }; + const component = shallowMount(ConfirmRollbackModal, { + propsData: { + environment, + }, + }); + const eventHubSpy = spyOn(eventHub, '$emit'); + const modal = component.find(GlModal); + modal.vm.$emit('ok'); + + expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', environment); + }); +}); diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/javascripts/environments/environment_rollback_spec.js index 79f33c5bc8a..8c47f6a12c0 100644 --- a/spec/javascripts/environments/environment_rollback_spec.js +++ b/spec/javascripts/environments/environment_rollback_spec.js @@ -1,8 +1,11 @@ import Vue from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import { GlButton } from '@gitlab/ui'; +import eventHub from '~/environments/event_hub'; import rollbackComp from '~/environments/components/environment_rollback.vue'; describe('Rollback Component', () => { - const retryURL = 'https://gitlab.com/retry'; + const retryUrl = 'https://gitlab.com/retry'; let RollbackComponent; beforeEach(() => { @@ -13,8 +16,9 @@ describe('Rollback Component', () => { const component = new RollbackComponent({ el: document.querySelector('.test-dom-element'), propsData: { - retryUrl: retryURL, + retryUrl, isLastDeployment: true, + environment: {}, }, }).$mount(); @@ -25,11 +29,33 @@ describe('Rollback Component', () => { const component = new RollbackComponent({ el: document.querySelector('.test-dom-element'), propsData: { - retryUrl: retryURL, + retryUrl, isLastDeployment: false, + environment: {}, }, }).$mount(); expect(component.$el).toHaveSpriteIcon('redo'); }); + + it('should emit a "rollback" event on button click', () => { + const eventHubSpy = spyOn(eventHub, '$emit'); + const component = shallowMount(RollbackComponent, { + propsData: { + retryUrl, + environment: { + name: 'test', + }, + }, + }); + const button = component.find(GlButton); + + button.vm.$emit('click'); + + expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', { + retryUrl, + isLastDeployment: true, + name: 'test', + }); + }); }); diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js index 08bbb390993..503af3920a8 100644 --- a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js +++ b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js @@ -1,7 +1,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; -import { GlButton, GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui'; +import { GlButton, GlEmptyState, GlLoadingIcon, GlTable, GlLink } from '@gitlab/ui'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -9,6 +9,7 @@ localVue.use(Vuex); describe('ErrorTrackingList', () => { let store; let wrapper; + let actions; function mountComponent({ errorTrackingEnabled = true } = {}) { wrapper = shallowMount(ErrorTrackingList, { @@ -20,12 +21,17 @@ describe('ErrorTrackingList', () => { errorTrackingEnabled, illustrationPath: 'illustration/path', }, + stubs: { + 'gl-link': GlLink, + }, }); } beforeEach(() => { - const actions = { + actions = { getErrorList: () => {}, + startPolling: () => {}, + restartPolling: jasmine.createSpy('restartPolling'), }; const state = { @@ -83,6 +89,18 @@ describe('ErrorTrackingList', () => { expect(wrapper.find(GlTable).exists()).toBeTruthy(); expect(wrapper.find(GlButton).exists()).toBeTruthy(); }); + + it('shows a message prompting to refresh', () => { + const refreshLink = wrapper.vm.$refs.empty.querySelector('a'); + + expect(refreshLink.textContent.trim()).toContain('Check again'); + }); + + it('restarts polling', () => { + wrapper.find('.js-try-again').trigger('click'); + + expect(actions.restartPolling).toHaveBeenCalled(); + }); }); describe('error tracking feature disabled', () => { diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index 30d68e7dc9d..12be3927e18 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -181,6 +181,18 @@ describe GlobalPolicy do end end + describe 'read instance metadata' do + context 'regular user' do + it { is_expected.to be_allowed(:read_instance_metadata) } + end + + context 'anonymous' do + let(:current_user) { nil } + + it { is_expected.not_to be_allowed(:read_instance_metadata) } + end + end + describe 'read instance statistics' do context 'regular user' do it { is_expected.to be_allowed(:read_instance_statistics) } diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb new file mode 100644 index 00000000000..4c56c559cf9 --- /dev/null +++ b/spec/requests/api/graphql/metadata_query_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting project information' do + include GraphqlHelpers + + let(:query) { graphql_query_for('metadata', {}, all_graphql_fields_for('Metadata')) } + + context 'logged in' do + it 'returns version and revision' do + post_graphql(query, current_user: create(:user)) + + expect(graphql_errors).to be_nil + expect(graphql_data).to eq( + 'metadata' => { + 'version' => Gitlab::VERSION, + 'revision' => Gitlab.revision + } + ) + end + end + + context 'anonymous user' do + it 'returns nothing' do + post_graphql(query, current_user: nil) + + expect(graphql_errors).to be_nil + expect(graphql_data).to eq('metadata' => nil) + end + end +end diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 38b618191fb..e06f8bbc095 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe API::Version do - describe 'GET /version' do + shared_examples_for 'GET /version' do context 'when unauthenticated' do it 'returns authentication error' do get api('/version') @@ -22,4 +22,20 @@ describe API::Version do end end end + + context 'with graphql enabled' do + before do + stub_feature_flags(graphql: true) + end + + include_examples 'GET /version' + end + + context 'with graphql disabled' do + before do + stub_feature_flags(graphql: false) + end + + include_examples 'GET /version' + end end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index e468ee4676d..d9529262483 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -77,8 +77,9 @@ module GraphqlHelpers def query_graphql_field(name, attributes = {}, fields = nil) fields ||= all_graphql_fields_for(name.classify) attributes = attributes_to_graphql(attributes) + attributes = "(#{attributes})" if attributes.present? <<~QUERY - #{name}(#{attributes}) { + #{name}#{attributes} { #{fields} } QUERY diff --git a/spec/support/pg_stat_activity.rb b/spec/support/pg_stat_activity.rb new file mode 100644 index 00000000000..f93fba08a19 --- /dev/null +++ b/spec/support/pg_stat_activity.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.before do + if Gitlab::Database.postgresql? && ENV['PG_STAT_WARNING_THRESHOLD'] + warning_threshold = ENV['PG_STAT_WARNING_THRESHOLD'].to_i + results = ActiveRecord::Base.connection.execute('SELECT * FROM pg_stat_activity') + ntuples = results.ntuples + + warn("pg_stat_activity count: #{ntuples}") + + if ntuples > warning_threshold + results.each do |result| + warn result.inspect + end + end + end + end +end diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb new file mode 100644 index 00000000000..54ec4f32856 --- /dev/null +++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'projects/deployments/_confirm_rollback_modal' do + let(:environment) { create(:environment, :with_review_app) } + let(:deployments) { environment.deployments } + let(:project) { environment.project } + + before do + assign(:environment, environment) + assign(:deployments, deployments) + assign(:project, project) + end + + context 'when re-deploying last deployment' do + let(:deployment) { deployments.first } + + before do + allow(view).to receive(:deployment).and_return(deployment) + end + + it 'shows "re-deploy"' do + render + + expect(rendered).to have_selector('h4', text: "Re-deploy environment #{environment.name}?") + expect(rendered).to have_selector('p', text: "This action will relaunch the job for commit #{deployment.short_sha}, putting the environment in a previous version. Are you sure you want to continue?") + expect(rendered).to have_selector('a.btn-danger', text: 'Re-deploy') + end + + it 'links to re-deploying the environment' do + expected_link = retry_project_job_path(environment.project, deployment.deployable) + + render + + expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Re-deploy') + end + end + + context 'when rolling back to previous deployment' do + let(:deployment) { create(:deployment, environment: environment) } + + before do + allow(view).to receive(:deployment).and_return(deployment) + end + + it 'shows "rollback"' do + render + + expect(rendered).to have_selector('h4', text: "Rollback environment #{environment.name}?") + expect(rendered).to have_selector('p', text: "This action will run the job defined by staging for commit #{deployment.short_sha}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?") + expect(rendered).to have_selector('a.btn-danger', text: 'Rollback') + end + + it 'links to re-deploying the environment' do + expected_link = retry_project_job_path(environment.project, deployment.deployable) + + render + + expect(rendered).to have_selector("a[href='#{expected_link}']", text: 'Rollback') + end + end +end diff --git a/yarn.lock b/yarn.lock index c045f313cff..ac39a27d659 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1454,6 +1454,13 @@ babel-loader@^8.0.5: mkdirp "^0.5.1" util.promisify "^1.0.0" +babel-plugin-dynamic-import-node@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz#c0adfb07d95f4a4495e9aaac6ec386c4d7c2524e" + integrity sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA== + dependencies: + object.assign "^4.1.0" + babel-plugin-istanbul@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.0.tgz#6892f529eff65a3e2d33d87dc5888ffa2ecd4a30" |