diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-08 15:07:59 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-08 15:07:59 +0300 |
commit | e3e300557f5def9bf2271735c8a620e6820dfada (patch) | |
tree | 8d0d4590518ee17eb32956e35637e11a2b8ca561 | |
parent | a821bd6ad17e304ca93838a411410a44ee9cff9f (diff) |
Add latest changes from gitlab-org/gitlab@master
59 files changed, 398 insertions, 446 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 18462cc24c7..a2591180039 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -484,6 +484,16 @@ export const historyPushState = newUrl => { }; /** + * Based on the current location and the string parameters provided + * overwrites the current entry in the history without reloading the page. + * + * @param {String} param + */ +export const historyReplaceState = newUrl => { + window.history.replaceState({}, document.title, newUrl); +}; + +/** * Returns true for a String value of "true" and false otherwise. * This is the opposite of Boolean(...).toString(). * `parseBoolean` is idempotent. diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js index fd72d2ddbe0..4b4a274794d 100644 --- a/app/assets/javascripts/pages/projects/pipelines/index/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js @@ -1,10 +1,18 @@ import Vue from 'vue'; +import { GlToast } from '@gitlab/ui'; +import { doesHashExistInUrl } from '~/lib/utils/url_utility'; +import { + parseBoolean, + historyReplaceState, + buildUrlWithCurrentLocation, +} from '~/lib/utils/common_utils'; +import { __ } from '~/locale'; import PipelinesStore from '../../../../pipelines/stores/pipelines_store'; import pipelinesComponent from '../../../../pipelines/components/pipelines.vue'; import Translate from '../../../../vue_shared/translate'; -import { parseBoolean } from '../../../../lib/utils/common_utils'; Vue.use(Translate); +Vue.use(GlToast); document.addEventListener( 'DOMContentLoaded', @@ -21,6 +29,11 @@ document.addEventListener( }, created() { this.dataset = document.querySelector(this.$options.el).dataset; + + if (doesHashExistInUrl('delete_success')) { + this.$toast.show(__('The pipeline has been deleted')); + historyReplaceState(buildUrlWithCurrentLocation()); + } }, render(createElement) { return createElement('pipelines-component', { diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index 39afa87afc3..726bba7f9f4 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -1,14 +1,17 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlModal } from '@gitlab/ui'; import ciHeader from '../../vue_shared/components/header_ci_component.vue'; import eventHub from '../event_hub'; import { __ } from '~/locale'; +const DELETE_MODAL_ID = 'pipeline-delete-modal'; + export default { name: 'PipelineHeaderSection', components: { ciHeader, GlLoadingIcon, + GlModal, }, props: { pipeline: { @@ -33,6 +36,11 @@ export default { shouldRenderContent() { return !this.isLoading && Object.keys(this.pipeline).length; }, + deleteModalConfirmationText() { + return __( + 'Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone.', + ); + }, }, watch: { @@ -42,6 +50,13 @@ export default { }, methods: { + onActionClicked(action) { + if (action.modal) { + this.$root.$emit('bv::show::modal', action.modal); + } else { + this.postAction(action); + } + }, postAction(action) { const index = this.actions.indexOf(action); @@ -49,6 +64,13 @@ export default { eventHub.$emit('headerPostAction', action); }, + deletePipeline() { + const index = this.actions.findIndex(action => action.modal === DELETE_MODAL_ID); + + this.$set(this.actions[index], 'isLoading', true); + + eventHub.$emit('headerDeleteAction', this.actions[index]); + }, getActions() { const actions = []; @@ -58,7 +80,6 @@ export default { label: __('Retry'), path: this.pipeline.retry_path, cssClass: 'js-retry-button btn btn-inverted-secondary', - type: 'button', isLoading: false, }); } @@ -68,7 +89,16 @@ export default { label: __('Cancel running'), path: this.pipeline.cancel_path, cssClass: 'js-btn-cancel-pipeline btn btn-danger', - type: 'button', + isLoading: false, + }); + } + + if (this.pipeline.delete_path) { + actions.push({ + label: __('Delete'), + path: this.pipeline.delete_path, + modal: DELETE_MODAL_ID, + cssClass: 'js-btn-delete-pipeline btn btn-danger btn-inverted', isLoading: false, }); } @@ -76,6 +106,7 @@ export default { return actions; }, }, + DELETE_MODAL_ID, }; </script> <template> @@ -88,8 +119,21 @@ export default { :user="pipeline.user" :actions="actions" item-name="Pipeline" - @actionClicked="postAction" + @actionClicked="onActionClicked" /> + <gl-loading-icon v-if="isLoading" :size="2" class="prepend-top-default append-bottom-default" /> + + <gl-modal + :modal-id="$options.DELETE_MODAL_ID" + :title="__('Delete pipeline')" + :ok-title="__('Delete pipeline')" + ok-variant="danger" + @ok="deletePipeline()" + > + <p> + {{ deleteModalConfirmationText }} + </p> + </gl-modal> </div> </template> diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index d8dbc3c2454..c874c4c6fdd 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -2,6 +2,7 @@ import Vue from 'vue'; import Flash from '~/flash'; import Translate from '~/vue_shared/translate'; import { __ } from '~/locale'; +import { setUrlFragment, redirectTo } from '~/lib/utils/url_utility'; import pipelineGraph from './components/graph/graph_component.vue'; import GraphBundleMixin from './mixins/graph_pipeline_bundle_mixin'; import PipelinesMediator from './pipeline_details_mediator'; @@ -62,9 +63,11 @@ export default () => { }, created() { eventHub.$on('headerPostAction', this.postAction); + eventHub.$on('headerDeleteAction', this.deleteAction); }, beforeDestroy() { eventHub.$off('headerPostAction', this.postAction); + eventHub.$off('headerDeleteAction', this.deleteAction); }, methods: { postAction(action) { @@ -73,6 +76,13 @@ export default () => { .then(() => this.mediator.refreshPipeline()) .catch(() => Flash(__('An error occurred while making the request.'))); }, + deleteAction(action) { + this.mediator.stopPipelinePoll(); + this.mediator.service + .deleteAction(action.path) + .then(({ request }) => redirectTo(setUrlFragment(request.responseURL, 'delete_success'))) + .catch(() => Flash(__('An error occurred while deleting the pipeline.'))); + }, }, render(createElement) { return createElement('pipeline-header', { diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js index bf021a0b447..f3387f00fc1 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js @@ -35,7 +35,7 @@ export default class pipelinesMediator { if (!Visibility.hidden()) { this.poll.restart(); } else { - this.poll.stop(); + this.stopPipelinePoll(); } }); } @@ -51,7 +51,7 @@ export default class pipelinesMediator { } refreshPipeline() { - this.poll.stop(); + this.stopPipelinePoll(); return this.service .getPipeline() @@ -64,6 +64,10 @@ export default class pipelinesMediator { ); } + stopPipelinePoll() { + this.poll.stop(); + } + /** * Backend expects paramets in the following format: `expanded[]=id&expanded[]=id` */ diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js index e44eb9cdfd1..ba2830ec596 100644 --- a/app/assets/javascripts/pipelines/services/pipeline_service.js +++ b/app/assets/javascripts/pipelines/services/pipeline_service.js @@ -10,6 +10,11 @@ export default class PipelineService { } // eslint-disable-next-line class-methods-use-this + deleteAction(endpoint) { + return axios.delete(`${endpoint}.json`); + } + + // eslint-disable-next-line class-methods-use-this postAction(endpoint) { return axios.post(`${endpoint}.json`); } diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index c652a684d7c..dba4a9231a1 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -117,28 +117,7 @@ export default { <section v-if="actions.length" class="header-action-buttons"> <template v-for="(action, i) in actions"> - <gl-link - v-if="action.type === 'link'" - :key="i" - :href="action.path" - :class="action.cssClass" - > - {{ action.label }} - </gl-link> - - <gl-link - v-else-if="action.type === 'ujs-link'" - :key="i" - :href="action.path" - :class="action.cssClass" - data-method="post" - rel="nofollow" - > - {{ action.label }} - </gl-link> - <loading-button - v-else-if="action.type === 'button'" :key="i" :loading="action.isLoading" :disabled="action.isLoading" diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index e3ef8f3f2ff..a62eb94a3e4 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -80,6 +80,12 @@ class Projects::PipelinesController < Projects::ApplicationController end end + def destroy + ::Ci::DestroyPipelineService.new(project, current_user).execute(pipeline) + + redirect_to project_pipelines_path(project), status: :see_other + end + def builds render_show end diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index 48da44123f6..5a0d53d9683 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -17,7 +17,7 @@ class PipelinesFinder return Ci::Pipeline.none end - items = pipelines.no_child + items = pipelines items = by_scope(items) items = by_status(items) items = by_ref(items) diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 123b8e75ad5..6c51f650b6a 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -54,10 +54,6 @@ module Ci def to_partial_path 'projects/generic_commit_statuses/generic_commit_status' end - - def yaml_for_downstream - nil - end end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index c8c1bbacad3..ab0a4fd6289 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -61,9 +61,7 @@ module Ci has_one :chat_data, class_name: 'Ci::PipelineChatData' has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline - has_many :child_pipelines, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :sourced_pipelines, source: :pipeline has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline - has_one :parent_pipeline, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :source_pipeline, source: :source_pipeline has_one :source_job, through: :source_pipeline, source: :source_job has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline @@ -215,7 +213,6 @@ module Ci end scope :internal, -> { where(source: internal_sources) } - scope :no_child, -> { where.not(source: :parent_pipeline) } scope :ci_sources, -> { where(config_source: ::Ci::PipelineEnums.ci_config_sources_values) } scope :for_user, -> (user) { where(user: user) } scope :for_sha, -> (sha) { where(sha: sha) } @@ -511,6 +508,10 @@ module Ci builds.skipped.after_stage(stage_idx).find_each(&:process) end + def child? + false + end + def latest? return false unless git_ref && commit.present? @@ -693,24 +694,6 @@ module Ci all_merge_requests.order(id: :desc) end - # If pipeline is a child of another pipeline, include the parent - # and the siblings, otherwise return only itself. - def same_family_pipeline_ids - if (parent = parent_pipeline) - [parent.id] + parent.child_pipelines.pluck(:id) - else - [self.id] - end - end - - def child? - parent_pipeline.present? - end - - def parent? - child_pipelines.exists? - end - def detailed_status(current_user) Gitlab::Ci::Status::Pipeline::Factory .new(self, current_user) diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index 24a26cb055c..3cd88807969 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -23,11 +23,10 @@ module Ci schedule: 4, api: 5, external: 6, - cross_project_pipeline: 7, + pipeline: 7, chat: 8, merge_request_event: 10, - external_pull_request_event: 11, - parent_pipeline: 12 + external_pull_request_event: 11 } end @@ -39,8 +38,7 @@ module Ci repository_source: 1, auto_devops_source: 2, remote_source: 4, - external_project_source: 5, - bridge_source: 6 + external_project_source: 5 } end diff --git a/app/models/ci/sources/pipeline.rb b/app/models/ci/sources/pipeline.rb index d71e3b55b9a..feaec27281c 100644 --- a/app/models/ci/sources/pipeline.rb +++ b/app/models/ci/sources/pipeline.rb @@ -18,8 +18,6 @@ module Ci validates :source_project, presence: true validates :source_job, presence: true validates :source_pipeline, presence: true - - scope :same_project, -> { where(arel_table[:source_project_id].eq(arel_table[:project_id])) } end end end diff --git a/app/serializers/pipeline_details_entity.rb b/app/serializers/pipeline_details_entity.rb index a4ab1d399bc..71589ac8315 100644 --- a/app/serializers/pipeline_details_entity.rb +++ b/app/serializers/pipeline_details_entity.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class PipelineDetailsEntity < PipelineEntity - expose :project, using: ProjectEntity - expose :flags do expose :latest?, as: :latest end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6b2a1bfe666..ba8f4fffe02 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -77,6 +77,10 @@ class PipelineEntity < Grape::Entity cancel_project_pipeline_path(pipeline.project, pipeline) end + expose :delete_path, if: -> (*) { can_delete? } do |pipeline| + project_pipeline_path(pipeline.project, pipeline) + end + expose :failed_builds, if: -> (*) { can_retry? }, using: JobEntity do |pipeline| pipeline.failed_builds end @@ -95,6 +99,10 @@ class PipelineEntity < Grape::Entity pipeline.cancelable? end + def can_delete? + can?(request.current_user, :destroy_pipeline, pipeline) + end + def has_presentable_merge_request? pipeline.triggered_by_merge_request? && can?(request.current_user, :read_merge_request, pipeline.merge_request) diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index be535a5d414..b25a1ea9209 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -41,7 +41,6 @@ class PipelineSerializer < BaseSerializer def preloaded_relations [ :latest_statuses_ordered_by_stage, - :project, :stages, { failed_builds: %i(project metadata) diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 2daf3a51235..ce3a9eb0772 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -23,7 +23,7 @@ module Ci Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze # rubocop: disable Metrics/ParameterLists - def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, bridge: nil, **options, &block) + def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, **options, &block) @pipeline = Ci::Pipeline.new command = Gitlab::Ci::Pipeline::Chain::Command.new( @@ -46,7 +46,6 @@ module Ci current_user: current_user, push_options: params[:push_options] || {}, chat_data: params[:chat_data], - bridge: bridge, **extra_options(options)) sequence = Gitlab::Ci::Pipeline::Chain::Sequence @@ -105,14 +104,14 @@ module Ci if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: true) project.ci_pipelines .where(ref: pipeline.ref) - .where.not(id: pipeline.same_family_pipeline_ids) + .where.not(id: pipeline.id) .where.not(sha: project.commit(pipeline.ref).try(:id)) .alive_or_scheduled .with_only_interruptible_builds else project.ci_pipelines .where(ref: pipeline.ref) - .where.not(id: pipeline.same_family_pipeline_ids) + .where.not(id: pipeline.id) .where.not(sha: project.commit(pipeline.ref).try(:id)) .created_or_pending end diff --git a/app/services/ci/pipeline_trigger_service.rb b/app/services/ci/pipeline_trigger_service.rb index d00d46b85f2..37b9b4c362c 100644 --- a/app/services/ci/pipeline_trigger_service.rb +++ b/app/services/ci/pipeline_trigger_service.rb @@ -44,7 +44,7 @@ module Ci return error("400 Job has to be running", 400) unless job.running? pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref]) - .execute(:cross_project_pipeline, ignore_skip_ci: true) do |pipeline| + .execute(:pipeline, ignore_skip_ci: true) do |pipeline| source = job.sourced_pipelines.build( source_pipeline: job.pipeline, source_project: job.project, diff --git a/changelogs/unreleased/118627-delete-selected-button-is-incorrectly-active-after-uploading-desig.yml b/changelogs/unreleased/118627-delete-selected-button-is-incorrectly-active-after-uploading-desig.yml new file mode 100644 index 00000000000..aada5d014a4 --- /dev/null +++ b/changelogs/unreleased/118627-delete-selected-button-is-incorrectly-active-after-uploading-desig.yml @@ -0,0 +1,5 @@ +--- +title: Fix Delete Selected button being active after uploading designs after a deletion +merge_request: 22516 +author: +type: fixed diff --git a/changelogs/unreleased/create-downstream-pipeline-in-same-project.yml b/changelogs/unreleased/create-downstream-pipeline-in-same-project.yml deleted file mode 100644 index 268d471eb0d..00000000000 --- a/changelogs/unreleased/create-downstream-pipeline-in-same-project.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow an upstream pipeline to create a downstream pipeline in the same project -merge_request: 20930 -author: -type: added diff --git a/changelogs/unreleased/feat-pipeline-ui-deletion.yml b/changelogs/unreleased/feat-pipeline-ui-deletion.yml new file mode 100644 index 00000000000..630b0057fa9 --- /dev/null +++ b/changelogs/unreleased/feat-pipeline-ui-deletion.yml @@ -0,0 +1,5 @@ +--- +title: Add pipeline deletion button to pipeline details page +merge_request: 21365 +author: Fabio Huser +type: added diff --git a/config/routes/project.rb b/config/routes/project.rb index df1b6cd5032..f339be7d0f5 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -343,7 +343,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do draw :merge_requests end - resources :pipelines, only: [:index, :new, :create, :show] do + resources :pipelines, only: [:index, :new, :create, :show, :destroy] do collection do resource :pipelines_settings, path: 'settings', only: [:show, :update] get :charts diff --git a/doc/ci/img/pipeline-delete.png b/doc/ci/img/pipeline-delete.png Binary files differnew file mode 100644 index 00000000000..65b42100099 --- /dev/null +++ b/doc/ci/img/pipeline-delete.png diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index d1e50039417..4d942ea3d54 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -305,12 +305,14 @@ For example, the query string ### Accessing pipelines You can find the current and historical pipeline runs under your project's -**CI/CD > Pipelines** page. Clicking on a pipeline will show the jobs that were run for -that pipeline. +**CI/CD > Pipelines** page. You can also access pipelines for a merge request by navigating +to its **Pipelines** tab. ![Pipelines index page](img/pipelines_index.png) -You can also access pipelines for a merge request by navigating to its **Pipelines** tab. +Clicking on a pipeline will bring you to the **Pipeline Details** page and show +the jobs that were run for that pipeline. From here you can cancel a running pipeline, +retry jobs on a failed pipeline, or [delete a pipeline](#deleting-a-single-pipeline). ### Accessing individual jobs @@ -410,6 +412,20 @@ This functionality is only available: - For users with at least Developer access. - If the the stage contains [manual actions](#manual-actions-from-pipeline-graphs). +### Deleting a single pipeline + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/24851) in GitLab 12.7. + +Users with [owner permissions](../user/permissions.md) in a project can delete a pipeline +by clicking on the pipeline in the **CI/CD > Pipelines** to get to the **Pipeline Details** +page, then using the **Delete** button. + +![Pipeline Delete Button](img/pipeline-delete.png) + +CAUTION: **Warning:** +Deleting a pipeline will expire all pipeline caches, and delete all related objects, +such as builds, logs, artifacts, and triggers. **This action cannot be undone.** + ## Most Recent Pipeline > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/50499) in GitLab 12.3. diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md index 3a96f8204fc..708003f2569 100644 --- a/doc/development/testing_guide/flaky_tests.md +++ b/doc/development/testing_guide/flaky_tests.md @@ -98,6 +98,10 @@ This was originally implemented in: <https://gitlab.com/gitlab-org/gitlab-foss/m - Memory is through the roof! (TL;DR: Load images but block images requests!): <https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/12003> +#### Capybara expectation times out + +- [Test imports a project (via Sidekiq) that is growing over time, leading to timeouts when the import takes longer than 60 seconds](https://gitlab.com/gitlab-org/gitlab/merge_requests/22599) + ## Resources - [Flaky Tests: Are You Sure You Want to Rerun Them?](http://semaphoreci.com/blog/2017/04/20/flaky-tests.html) diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index 0f355906948..c2df419cca0 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -10,7 +10,7 @@ module Gitlab :trigger_request, :schedule, :merge_request, :external_pull_request, :ignore_skip_ci, :save_incompleted, :seeds_block, :variables_attributes, :push_options, - :chat_data, :allow_mirror_update, :bridge, + :chat_data, :allow_mirror_update, # These attributes are set by Chains during processing: :config_content, :config_processor, :stage_seeds ) do diff --git a/lib/gitlab/ci/pipeline/chain/config/content.rb b/lib/gitlab/ci/pipeline/chain/config/content.rb index 66bead3a416..f9fffbcb517 100644 --- a/lib/gitlab/ci/pipeline/chain/config/content.rb +++ b/lib/gitlab/ci/pipeline/chain/config/content.rb @@ -9,7 +9,7 @@ module Gitlab include Chain::Helpers SOURCES = [ - Gitlab::Ci::Pipeline::Chain::Config::Content::Bridge, + Gitlab::Ci::Pipeline::Chain::Config::Content::Runtime, Gitlab::Ci::Pipeline::Chain::Config::Content::Repository, Gitlab::Ci::Pipeline::Chain::Config::Content::ExternalProject, Gitlab::Ci::Pipeline::Chain::Config::Content::Remote, @@ -17,7 +17,7 @@ module Gitlab ].freeze LEGACY_SOURCES = [ - Gitlab::Ci::Pipeline::Chain::Config::Content::Bridge, + Gitlab::Ci::Pipeline::Chain::Config::Content::Runtime, Gitlab::Ci::Pipeline::Chain::Config::Content::LegacyRepository, Gitlab::Ci::Pipeline::Chain::Config::Content::LegacyAutoDevops ].freeze diff --git a/lib/gitlab/ci/pipeline/chain/config/content/bridge.rb b/lib/gitlab/ci/pipeline/chain/config/content/bridge.rb deleted file mode 100644 index 39ffa2d4e25..00000000000 --- a/lib/gitlab/ci/pipeline/chain/config/content/bridge.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Ci - module Pipeline - module Chain - module Config - class Content - class Bridge < Source - def content - return unless @command.bridge - - @command.bridge.yaml_for_downstream - end - - def source - :bridge_source - end - end - end - end - end - end - end -end diff --git a/lib/gitlab/ci/pipeline/chain/config/content/runtime.rb b/lib/gitlab/ci/pipeline/chain/config/content/runtime.rb new file mode 100644 index 00000000000..4811d3d913d --- /dev/null +++ b/lib/gitlab/ci/pipeline/chain/config/content/runtime.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Pipeline + module Chain + module Config + class Content + class Runtime < Source + def content + @command.config_content + end + + def source + # The only case when this source is used is when the config content + # is passed in as parameter to Ci::CreatePipelineService. + # This would only occur with parent/child pipelines which is being + # implemented. + # TODO: change source to return :runtime_source + # https://gitlab.com/gitlab-org/gitlab/merge_requests/21041 + + nil + end + end + end + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9830a6ce80d..c584defb2e4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1631,6 +1631,9 @@ msgstr "" msgid "An error occurred while deleting the comment" msgstr "" +msgid "An error occurred while deleting the pipeline." +msgstr "" + msgid "An error occurred while detecting host keys" msgstr "" @@ -2092,6 +2095,9 @@ msgstr "" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "" +msgid "Are you sure you want to delete this pipeline? Doing so will expire all pipeline caches and delete all related objects, such as builds, logs, artifacts, and triggers. This action cannot be undone." +msgstr "" + msgid "Are you sure you want to erase this build?" msgstr "" @@ -3266,6 +3272,9 @@ msgstr "" msgid "Checkout" msgstr "" +msgid "Checkout|%{selectedPlanText} plan" +msgstr "" + msgid "Checkout|1. Your profile" msgstr "" @@ -3278,9 +3287,36 @@ msgstr "" msgid "Checkout|Checkout" msgstr "" +msgid "Checkout|Continue to billing" +msgstr "" + msgid "Checkout|Edit" msgstr "" +msgid "Checkout|GitLab plan" +msgstr "" + +msgid "Checkout|Group" +msgstr "" + +msgid "Checkout|Name of company or organization using GitLab" +msgstr "" + +msgid "Checkout|Need more users? Purchase GitLab for your %{company}." +msgstr "" + +msgid "Checkout|Number of users" +msgstr "" + +msgid "Checkout|Subscription details" +msgstr "" + +msgid "Checkout|Users" +msgstr "" + +msgid "Checkout|company or team" +msgstr "" + msgid "Cherry-pick this commit" msgstr "" @@ -5735,6 +5771,9 @@ msgstr "" msgid "Delete list" msgstr "" +msgid "Delete pipeline" +msgstr "" + msgid "Delete snippet" msgstr "" @@ -18098,6 +18137,9 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "" +msgid "The pipeline has been deleted" +msgstr "" + msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." msgstr "" diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 902a84a843b..4cc5b3cba7c 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -740,4 +740,51 @@ describe Projects::PipelinesController do expect(response).to have_gitlab_http_status(404) end end + + describe 'DELETE #destroy' do + let!(:project) { create(:project, :private, :repository) } + let!(:pipeline) { create(:ci_pipeline, :failed, project: project) } + let!(:build) { create(:ci_build, :failed, pipeline: pipeline) } + + context 'when user has ability to delete pipeline' do + before do + sign_in(project.owner) + end + + it 'deletes pipeline and redirects' do + delete_pipeline + + expect(response).to have_gitlab_http_status(303) + + expect(Ci::Build.exists?(build.id)).to be_falsy + expect(Ci::Pipeline.exists?(pipeline.id)).to be_falsy + end + + context 'and builds are disabled' do + let(:feature) { ProjectFeature::DISABLED } + + it 'fails to delete pipeline' do + delete_pipeline + + expect(response).to have_gitlab_http_status(404) + end + end + end + + context 'when user has no privileges' do + it 'fails to delete pipeline' do + delete_pipeline + + expect(response).to have_gitlab_http_status(403) + end + end + + def delete_pipeline + delete :destroy, params: { + namespace_id: project.namespace, + project_id: project, + id: pipeline.id + } + end + end end diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb index 89bf69dea7d..36478128dd1 100644 --- a/spec/features/import/manifest_import_spec.rb +++ b/spec/features/import/manifest_import_spec.rb @@ -24,17 +24,17 @@ describe 'Import multiple repositories by uploading a manifest file', :js do expect(page).to have_content('https://android-review.googlesource.com/platform/build/blueprint') end - it 'imports successfully imports a project', :sidekiq_might_not_need_inline do + it 'imports successfully imports a project', :sidekiq_inline do visit new_import_manifest_path attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml')) click_on 'List available repositories' - page.within(first_row) do + page.within(second_row) do click_on 'Import' expect(page).to have_content 'Done' - expect(page).to have_content("#{group.full_path}/build/make") + expect(page).to have_content("#{group.full_path}/build/blueprint") end end @@ -47,7 +47,7 @@ describe 'Import multiple repositories by uploading a manifest file', :js do expect(page).to have_content 'The uploaded file is not a valid XML file.' end - def first_row - page.all('table.import-jobs tbody tr')[0] + def second_row + page.all('table.import-jobs tbody tr')[1] end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 4b97c58d920..198af65c361 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -59,7 +59,8 @@ describe 'Pipeline', :js do describe 'GET /:project/pipelines/:id' do include_context 'pipeline builds' - let(:project) { create(:project, :repository) } + let(:group) { create(:group) } + let(:project) { create(:project, :repository, group: group) } let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) } subject(:visit_pipeline) { visit project_pipeline_path(project, pipeline) } @@ -329,6 +330,32 @@ describe 'Pipeline', :js do end end + context 'deleting pipeline' do + context 'when user can not delete' do + before do + visit_pipeline + end + + it { expect(page).not_to have_button('Delete') } + end + + context 'when deleting' do + before do + group.add_owner(user) + + visit_pipeline + + click_button 'Delete' + click_button 'Delete pipeline' + end + + it 'redirects to pipeline overview page', :sidekiq_might_not_need_inline do + expect(page).to have_content('The pipeline has been deleted') + expect(current_path).to eq(project_pipelines_path(project)) + end + end + end + context 'when pipeline ref does not exist in repository anymore' do let(:pipeline) do create(:ci_empty_pipeline, project: project, diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index 1dbf9491118..c8a4ea799c3 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -64,19 +64,6 @@ describe PipelinesFinder do end end - context 'when project has child pipelines' do - let!(:parent_pipeline) { create(:ci_pipeline, project: project) } - let!(:child_pipeline) { create(:ci_pipeline, project: project, source: :parent_pipeline) } - - let!(:pipeline_source) do - create(:ci_sources_pipeline, pipeline: child_pipeline, source_pipeline: parent_pipeline) - end - - it 'filters out child pipelines and show only the parents' do - is_expected.to eq([parent_pipeline]) - end - end - HasStatus::AVAILABLE_STATUSES.each do |target| context "when status is #{target}" do let(:params) { { status: target } } diff --git a/spec/frontend/confidential_merge_request/components/project_form_group_spec.js b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js index 3001363f7b9..975701ebd96 100644 --- a/spec/frontend/confidential_merge_request/components/project_form_group_spec.js +++ b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js @@ -1,9 +1,8 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import ProjectFormGroup from '~/confidential_merge_request/components/project_form_group.vue'; -const localVue = createLocalVue(); const mockData = [ { id: 1, @@ -30,7 +29,6 @@ function factory(projects = mockData) { mock.onGet(/api\/(.*)\/projects\/gitlab-org%2Fgitlab-ce\/forks/).reply(200, projects); vm = shallowMount(ProjectFormGroup, { - localVue, propsData: { namespacePath: 'gitlab-org', projectPath: 'gitlab-org/gitlab-ce', @@ -49,7 +47,7 @@ describe('Confidential merge request project form group component', () => { it('renders fork dropdown', () => { factory(); - return localVue.nextTick(() => { + return vm.vm.$nextTick(() => { expect(vm.element).toMatchSnapshot(); }); }); @@ -57,7 +55,7 @@ describe('Confidential merge request project form group component', () => { it('sets selected project as first fork', () => { factory(); - return localVue.nextTick(() => { + return vm.vm.$nextTick(() => { expect(vm.vm.selectedProject).toEqual({ id: 1, name: 'root / gitlab-ce', @@ -70,7 +68,7 @@ describe('Confidential merge request project form group component', () => { it('renders empty state when response is empty', () => { factory([]); - return localVue.nextTick(() => { + return vm.vm.$nextTick(() => { expect(vm.element).toMatchSnapshot(); }); }); diff --git a/spec/frontend/contributors/component/contributors_spec.js b/spec/frontend/contributors/component/contributors_spec.js index 1d5605ef516..3e4924ed906 100644 --- a/spec/frontend/contributors/component/contributors_spec.js +++ b/spec/frontend/contributors/component/contributors_spec.js @@ -1,11 +1,10 @@ import Vue from 'vue'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { createStore } from '~/contributors/stores'; import axios from '~/lib/utils/axios_utils'; import ContributorsCharts from '~/contributors/components/contributors.vue'; -const localVue = createLocalVue(); let wrapper; let mock; let store; @@ -52,7 +51,7 @@ describe('Contributors charts', () => { it('should display loader whiled loading data', () => { wrapper.vm.$store.state.loading = true; - return localVue.nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(wrapper.find('.contributors-loader').exists()).toBe(true); }); }); @@ -60,7 +59,7 @@ describe('Contributors charts', () => { it('should render charts when loading completed and there is chart data', () => { wrapper.vm.$store.state.loading = false; wrapper.vm.$store.state.chartData = chartData; - return localVue.nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(wrapper.find('.contributors-loader').exists()).toBe(false); expect(wrapper.find('.contributors-charts').exists()).toBe(true); expect(wrapper.element).toMatchSnapshot(); diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js index eb9effea736..6ad33c36b30 100644 --- a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js +++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js @@ -1,8 +1,7 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; import discussionsMockData from '../mock_data/diff_discussions'; -const localVue = createLocalVue(); const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; describe('DiffGutterAvatars', () => { @@ -14,7 +13,6 @@ describe('DiffGutterAvatars', () => { const createComponent = (props = {}) => { wrapper = shallowMount(DiffGutterAvatars, { - localVue, propsData: { ...props, }, diff --git a/spec/frontend/diffs/components/edit_button_spec.js b/spec/frontend/diffs/components/edit_button_spec.js index fc53f26bd42..9bcb23f3cbf 100644 --- a/spec/frontend/diffs/components/edit_button_spec.js +++ b/spec/frontend/diffs/components/edit_button_spec.js @@ -1,7 +1,6 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import EditButton from '~/diffs/components/edit_button.vue'; -const localVue = createLocalVue(); const editPath = 'test-path'; describe('EditButton', () => { @@ -9,7 +8,6 @@ describe('EditButton', () => { const createComponent = (props = {}) => { wrapper = shallowMount(EditButton, { - localVue, propsData: { ...props }, sync: false, attachToDocument: true, diff --git a/spec/frontend/diffs/components/hidden_files_warning_spec.js b/spec/frontend/diffs/components/hidden_files_warning_spec.js index 5bf5ddd27bd..164ff95de6d 100644 --- a/spec/frontend/diffs/components/hidden_files_warning_spec.js +++ b/spec/frontend/diffs/components/hidden_files_warning_spec.js @@ -1,7 +1,6 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue'; -const localVue = createLocalVue(); const propsData = { total: '10', visible: 5, @@ -14,7 +13,6 @@ describe('HiddenFilesWarning', () => { const createComponent = () => { wrapper = shallowMount(HiddenFilesWarning, { - localVue, sync: false, propsData, }); diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js index e45d34bf9d5..245651af61c 100644 --- a/spec/frontend/diffs/components/no_changes_spec.js +++ b/spec/frontend/diffs/components/no_changes_spec.js @@ -13,7 +13,7 @@ describe('Diff no changes empty state', () => { const store = createStore(); extendStore(store); - vm = shallowMount(localVue.extend(NoChanges), { + vm = shallowMount(NoChanges, { localVue, store, propsData: { diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js index 4e0e8a9f0e3..e0574450101 100644 --- a/spec/frontend/ide/components/ide_status_list_spec.js +++ b/spec/frontend/ide/components/ide_status_list_spec.js @@ -25,7 +25,7 @@ describe('ide/components/ide_status_list', () => { }, }); - wrapper = shallowMount(localVue.extend(IdeStatusList), { + wrapper = shallowMount(IdeStatusList, { localVue, sync: false, store, diff --git a/spec/frontend/issuables_list/components/issuables_list_app_spec.js b/spec/frontend/issuables_list/components/issuables_list_app_spec.js index 621e8b8aa54..4a2768b9389 100644 --- a/spec/frontend/issuables_list/components/issuables_list_app_spec.js +++ b/spec/frontend/issuables_list/components/issuables_list_app_spec.js @@ -1,6 +1,6 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { GlEmptyState, GlPagination, GlSkeletonLoading } from '@gitlab/ui'; import waitForPromises from 'helpers/wait_for_promises'; import { TEST_HOST } from 'helpers/test_constants'; @@ -18,8 +18,6 @@ const TEST_ENDPOINT = '/issues'; const TEST_CREATE_ISSUES_PATH = '/createIssue'; const TEST_EMPTY_SVG_PATH = '/emptySvg'; -const localVue = createLocalVue(); - const MOCK_ISSUES = Array(PAGE_SIZE_MANUAL) .fill(0) .map((_, i) => ({ @@ -40,14 +38,13 @@ describe('Issuables list component', () => { }; const factory = (props = { sortKey: 'priority' }) => { - wrapper = shallowMount(localVue.extend(IssuablesListApp), { + wrapper = shallowMount(IssuablesListApp, { propsData: { endpoint: TEST_ENDPOINT, createIssuePath: TEST_CREATE_ISSUES_PATH, emptySvgPath: TEST_EMPTY_SVG_PATH, ...props, }, - localVue, sync: false, attachToDocument: true, }); diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js index 77da3390918..bcc131d54c2 100644 --- a/spec/frontend/issue_show/components/pinned_links_spec.js +++ b/spec/frontend/issue_show/components/pinned_links_spec.js @@ -1,9 +1,7 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { GlLink } from '@gitlab/ui'; import PinnedLinks from '~/issue_show/components/pinned_links.vue'; -const localVue = createLocalVue(); - const plainZoomUrl = 'https://zoom.us/j/123456789'; describe('PinnedLinks', () => { @@ -19,8 +17,7 @@ describe('PinnedLinks', () => { }; const createComponent = props => { - wrapper = shallowMount(localVue.extend(PinnedLinks), { - localVue, + wrapper = shallowMount(PinnedLinks, { sync: false, propsData: { zoomMeetingUrl: null, diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js index b4539801e0f..2f43c442600 100644 --- a/spec/frontend/monitoring/components/charts/column_spec.js +++ b/spec/frontend/monitoring/components/charts/column_spec.js @@ -1,9 +1,7 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { GlColumnChart } from '@gitlab/ui/dist/charts'; import ColumnChart from '~/monitoring/components/charts/column.vue'; -const localVue = createLocalVue(); - jest.mock('~/lib/utils/icon_utils', () => ({ getSvgIconPathContent: jest.fn().mockResolvedValue('mockSvgPathContent'), })); @@ -12,7 +10,7 @@ describe('Column component', () => { let columnChart; beforeEach(() => { - columnChart = shallowMount(localVue.extend(ColumnChart), { + columnChart = shallowMount(ColumnChart, { propsData: { graphData: { metrics: [ @@ -35,7 +33,6 @@ describe('Column component', () => { containerWidth: 100, }, sync: false, - localVue, }); }); diff --git a/spec/frontend/monitoring/components/charts/empty_chart_spec.js b/spec/frontend/monitoring/components/charts/empty_chart_spec.js index 06822126b59..c7f0627f151 100644 --- a/spec/frontend/monitoring/components/charts/empty_chart_spec.js +++ b/spec/frontend/monitoring/components/charts/empty_chart_spec.js @@ -1,19 +1,16 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; -const localVue = createLocalVue(); - describe('Empty Chart component', () => { let emptyChart; const graphTitle = 'Memory Usage'; beforeEach(() => { - emptyChart = shallowMount(localVue.extend(EmptyChart), { + emptyChart = shallowMount(EmptyChart, { propsData: { graphTitle, }, sync: false, - localVue, }); }); diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js index 78bcc400787..dfea4eb41b1 100644 --- a/spec/frontend/monitoring/components/charts/single_stat_spec.js +++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js @@ -1,19 +1,16 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import SingleStatChart from '~/monitoring/components/charts/single_stat.vue'; import { graphDataPrometheusQuery } from '../../mock_data'; -const localVue = createLocalVue(); - describe('Single Stat Chart component', () => { let singleStatChart; beforeEach(() => { - singleStatChart = shallowMount(localVue.extend(SingleStatChart), { + singleStatChart = shallowMount(SingleStatChart, { propsData: { graphData: graphDataPrometheusQuery, }, sync: false, - localVue, }); }); diff --git a/spec/frontend/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js index bb6e029c808..89db03378db 100644 --- a/spec/frontend/operation_settings/components/external_dashboard_spec.js +++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js @@ -1,4 +1,4 @@ -import { mount, shallowMount, createLocalVue } from '@vue/test-utils'; +import { mount, shallowMount } from '@vue/test-utils'; import { GlButton, GlLink, GlFormGroup, GlFormInput } from '@gitlab/ui'; import { TEST_HOST } from 'helpers/test_constants'; import ExternalDashboard from '~/operation_settings/components/external_dashboard.vue'; @@ -15,12 +15,10 @@ describe('operation settings external dashboard component', () => { const operationsSettingsEndpoint = `${TEST_HOST}/mock/ops/settings/endpoint`; const externalDashboardUrl = `http://mock-external-domain.com/external/dashboard/url`; const externalDashboardHelpPagePath = `${TEST_HOST}/help/page/path`; - const localVue = createLocalVue(); const mountComponent = (shallow = true) => { const config = [ ExternalDashboard, { - localVue, store: store({ operationsSettingsEndpoint, externalDashboardUrl, diff --git a/spec/frontend/registry/list/components/app_spec.js b/spec/frontend/registry/list/components/app_spec.js index 5072a285f83..ac95c4dddcf 100644 --- a/spec/frontend/registry/list/components/app_spec.js +++ b/spec/frontend/registry/list/components/app_spec.js @@ -1,4 +1,3 @@ -import Vue from 'vue'; import { mount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; import registry from '~/registry/list/components/app.vue'; @@ -35,12 +34,8 @@ describe('Registry List', () => { }; beforeEach(() => { - // This is needed due to console.error called by vue to emit a warning that stop the tests. - // See https://github.com/vuejs/vue-test-utils/issues/532. - Vue.config.silent = true; wrapper = mount(registry, { attachToDocument: true, - sync: false, propsData, computed: { repos() { @@ -52,7 +47,6 @@ describe('Registry List', () => { }); afterEach(() => { - Vue.config.silent = false; wrapper.destroy(); }); @@ -138,7 +132,7 @@ describe('Registry List', () => { wrapper = mount(registry, { propsData: { ...propsData, - endpoint: null, + endpoint: '', isGroupPage, }, methods, @@ -146,7 +140,7 @@ describe('Registry List', () => { }); it('call the right vuex setters', () => { - expect(methods.setMainEndpoint).toHaveBeenLastCalledWith(null); + expect(methods.setMainEndpoint).toHaveBeenLastCalledWith(''); expect(methods.setIsDeleteDisabled).toHaveBeenLastCalledWith(true); }); diff --git a/spec/frontend/registry/list/components/collapsible_container_spec.js b/spec/frontend/registry/list/components/collapsible_container_spec.js index c7cf36fe151..1768df89432 100644 --- a/spec/frontend/registry/list/components/collapsible_container_spec.js +++ b/spec/frontend/registry/list/components/collapsible_container_spec.js @@ -1,4 +1,3 @@ -import Vue from 'vue'; import Vuex from 'vuex'; import { mount, createLocalVue } from '@vue/test-utils'; import createFlash from '~/flash'; @@ -28,14 +27,10 @@ describe('collapsible registry container', () => { store, localVue, attachToDocument: true, - sync: false, }); beforeEach(() => { createFlash.mockClear(); - // This is needed due to console.error called by vue to emit a warning that stop the tests - // see https://github.com/vuejs/vue-test-utils/issues/532 - Vue.config.silent = true; store = new Vuex.Store({ state: { isDeleteDisabled: false, @@ -51,7 +46,6 @@ describe('collapsible registry container', () => { }); afterEach(() => { - Vue.config.silent = false; wrapper.destroy(); }); @@ -72,25 +66,23 @@ describe('collapsible registry container', () => { expectIsClosed(); }); - it('should be open when user clicks on closed repo', done => { + it('should be open when user clicks on closed repo', () => { const toggleRepos = findToggleRepos(); toggleRepos.at(0).trigger('click'); - Vue.nextTick(() => { + return wrapper.vm.$nextTick().then(() => { const container = findContainerImageTags(); expect(container.exists()).toBe(true); expect(wrapper.vm.fetchList).toHaveBeenCalled(); - done(); }); }); - it('should be closed when the user clicks on an opened repo', done => { + it('should be closed when the user clicks on an opened repo', () => { const toggleRepos = findToggleRepos(); toggleRepos.at(0).trigger('click'); - Vue.nextTick(() => { + return wrapper.vm.$nextTick().then(() => { toggleRepos.at(0).trigger('click'); - Vue.nextTick(() => { + wrapper.vm.$nextTick(() => { expectIsClosed(); - done(); }); }); }); diff --git a/spec/frontend/registry/list/components/table_registry_spec.js b/spec/frontend/registry/list/components/table_registry_spec.js index 1b545eea034..935f93db883 100644 --- a/spec/frontend/registry/list/components/table_registry_spec.js +++ b/spec/frontend/registry/list/components/table_registry_spec.js @@ -1,4 +1,3 @@ -import Vue from 'vue'; import Vuex from 'vuex'; import { mount, createLocalVue } from '@vue/test-utils'; import createFlash from '~/flash'; @@ -29,13 +28,9 @@ describe('table registry', () => { const bulkDeletePath = 'path'; const mountWithStore = config => - mount(tableRegistry, { ...config, store, localVue, attachToDocument: true, sync: false }); + mount(tableRegistry, { ...config, store, localVue, attachToDocument: true }); beforeEach(() => { - // This is needed due to console.error called by vue to emit a warning that stop the tests - // see https://github.com/vuejs/vue-test-utils/issues/532 - Vue.config.silent = true; - store = new Vuex.Store({ state: { isDeleteDisabled: false, @@ -52,7 +47,6 @@ describe('table registry', () => { }); afterEach(() => { - Vue.config.silent = false; wrapper.destroy(); }); @@ -82,53 +76,53 @@ describe('table registry', () => { }); describe('multi select', () => { - it('selecting a row should enable delete button', done => { + it('selecting a row should enable delete button', () => { const deleteBtn = findDeleteButton(); const checkboxes = findSelectCheckboxes(); expect(deleteBtn.attributes('disabled')).toBe('disabled'); checkboxes.at(0).trigger('click'); - Vue.nextTick(() => { + return wrapper.vm.$nextTick().then(() => { expect(deleteBtn.attributes('disabled')).toEqual(undefined); - done(); }); }); - it('selecting all checkbox should select all rows and enable delete button', done => { + it('selecting all checkbox should select all rows and enable delete button', () => { const selectAll = findSelectAllCheckbox(); selectAll.trigger('click'); - Vue.nextTick(() => { + return wrapper.vm.$nextTick().then(() => { const checkboxes = findSelectCheckboxes(); const checked = checkboxes.filter(w => w.element.checked); expect(checked.length).toBe(checkboxes.length); - done(); }); }); - it('deselecting select all checkbox should deselect all rows and disable delete button', done => { + it('deselecting select all checkbox should deselect all rows and disable delete button', () => { const checkboxes = findSelectCheckboxes(); const selectAll = findSelectAllCheckbox(); selectAll.trigger('click'); selectAll.trigger('click'); - Vue.nextTick(() => { + return wrapper.vm.$nextTick().then(() => { const checked = checkboxes.filter(w => !w.element.checked); expect(checked.length).toBe(checkboxes.length); - done(); }); }); - it('should delete multiple items when multiple items are selected', done => { + it('should delete multiple items when multiple items are selected', () => { const multiDeleteItems = jest.fn().mockResolvedValue(); wrapper.setMethods({ multiDeleteItems }); - Vue.nextTick(() => { - const selectAll = findSelectAllCheckbox(); - selectAll.trigger('click'); - - Vue.nextTick(() => { + return wrapper.vm + .$nextTick() + .then(() => { + const selectAll = findSelectAllCheckbox(); + selectAll.trigger('click'); + return wrapper.vm.$nextTick(); + }) + .then(() => { const deleteBtn = findDeleteButton(); expect(wrapper.vm.selectedItems).toEqual([0, 1]); expect(deleteBtn.attributes('disabled')).toEqual(undefined); @@ -140,9 +134,7 @@ describe('table registry', () => { path: bulkDeletePath, items: [firstImage.tag, secondImage.tag], }); - done(); }); - }); }); it('should show an error message if bulkDeletePath is not set', () => { diff --git a/spec/frontend/releases/list/components/evidence_block_spec.js b/spec/frontend/releases/list/components/evidence_block_spec.js index 1be6083f4ab..39f3975f665 100644 --- a/spec/frontend/releases/list/components/evidence_block_spec.js +++ b/spec/frontend/releases/list/components/evidence_block_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { GlLink } from '@gitlab/ui'; import { truncateSha } from '~/lib/utils/text_utility'; import Icon from '~/vue_shared/components/icon.vue'; @@ -10,10 +10,7 @@ describe('Evidence Block', () => { let wrapper; const factory = (options = {}) => { - const localVue = createLocalVue(); - - wrapper = mount(localVue.extend(EvidenceBlock), { - localVue, + wrapper = mount(EvidenceBlock, { ...options, }); }; diff --git a/spec/frontend/serverless/components/environment_row_spec.js b/spec/frontend/serverless/components/environment_row_spec.js index 866b2165917..21637473e42 100644 --- a/spec/frontend/serverless/components/environment_row_spec.js +++ b/spec/frontend/serverless/components/environment_row_spec.js @@ -1,20 +1,18 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import environmentRowComponent from '~/serverless/components/environment_row.vue'; import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; import { translate } from '~/serverless/utils'; -const createComponent = (localVue, env, envName) => - shallowMount(environmentRowComponent, { localVue, propsData: { env, envName }, sync: false }).vm; +const createComponent = (env, envName) => + shallowMount(environmentRowComponent, { propsData: { env, envName }, sync: false }).vm; describe('environment row component', () => { describe('default global cluster case', () => { - let localVue; let vm; beforeEach(() => { - localVue = createLocalVue(); - vm = createComponent(localVue, translate(mockServerlessFunctions.functions)['*'], '*'); + vm = createComponent(translate(mockServerlessFunctions.functions)['*'], '*'); }); afterEach(() => vm.$destroy()); @@ -44,15 +42,9 @@ describe('environment row component', () => { describe('default named cluster case', () => { let vm; - let localVue; beforeEach(() => { - localVue = createLocalVue(); - vm = createComponent( - localVue, - translate(mockServerlessFunctionsDiffEnv.functions).test, - 'test', - ); + vm = createComponent(translate(mockServerlessFunctionsDiffEnv.functions).test, 'test'); }); afterEach(() => vm.$destroy()); diff --git a/spec/javascripts/pipelines/header_component_spec.js b/spec/javascripts/pipelines/header_component_spec.js index 556a0976b29..8c033447ce4 100644 --- a/spec/javascripts/pipelines/header_component_spec.js +++ b/spec/javascripts/pipelines/header_component_spec.js @@ -34,6 +34,7 @@ describe('Pipeline details header', () => { avatar_url: 'link', }, retry_path: 'path', + delete_path: 'path', }, isLoading: false, }; @@ -55,12 +56,22 @@ describe('Pipeline details header', () => { }); describe('action buttons', () => { - it('should call postAction when button action is clicked', () => { + it('should call postAction when retry button action is clicked', done => { eventHub.$on('headerPostAction', action => { expect(action.path).toEqual('path'); + done(); }); - vm.$el.querySelector('button').click(); + vm.$el.querySelector('.js-retry-button').click(); + }); + + it('should fire modal event when delete button action is clicked', done => { + vm.$root.$on('bv::modal::show', action => { + expect(action.componentId).toEqual('pipeline-delete-modal'); + done(); + }); + + vm.$el.querySelector('.js-btn-delete-pipeline').click(); }); }); }); diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index 7bd5e5a64b1..ea2eed2886a 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -31,17 +31,9 @@ describe('Header CI Component', () => { { label: 'Retry', path: 'path', - type: 'button', cssClass: 'btn', isLoading: false, }, - { - label: 'Go', - path: 'path', - type: 'link', - cssClass: 'link', - isLoading: false, - }, ], hasSidebarButton: true, }; @@ -77,11 +69,10 @@ describe('Header CI Component', () => { }); it('should render provided actions', () => { - expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON'); - expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label); - expect(vm.$el.querySelector('.link').tagName).toEqual('A'); - expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label); - expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path); + const btn = vm.$el.querySelector('.btn'); + + expect(btn.tagName).toEqual('BUTTON'); + expect(btn.textContent.trim()).toEqual(props.actions[0].label); }); it('should show loading icon', done => { diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb index 4c4359ad5d2..aaea044595f 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb @@ -15,42 +15,6 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do stub_feature_flags(ci_root_config_content: false) end - context 'when bridge job is passed in as parameter' do - let(:ci_config_path) { nil } - let(:bridge) { create(:ci_bridge) } - - before do - command.bridge = bridge - end - - context 'when bridge job has downstream yaml' do - before do - allow(bridge).to receive(:yaml_for_downstream).and_return('the-yaml') - end - - it 'returns the content already available in command' do - subject.perform! - - expect(pipeline.config_source).to eq 'bridge_source' - expect(command.config_content).to eq 'the-yaml' - end - end - - context 'when bridge job does not have downstream yaml' do - before do - allow(bridge).to receive(:yaml_for_downstream).and_return(nil) - end - - it 'returns the next available source' do - subject.perform! - - expect(pipeline.config_source).to eq 'auto_devops_source' - template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps') - expect(command.config_content).to eq(template.content) - end - end - end - context 'when config is defined in a custom path in the repository' do let(:ci_config_path) { 'path/to/config.yml' } @@ -171,23 +135,6 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do end end - context 'when bridge job is passed in as parameter' do - let(:ci_config_path) { nil } - let(:bridge) { create(:ci_bridge) } - - before do - command.bridge = bridge - allow(bridge).to receive(:yaml_for_downstream).and_return('the-yaml') - end - - it 'returns the content already available in command' do - subject.perform! - - expect(pipeline.config_source).to eq 'bridge_source' - expect(command.config_content).to eq 'the-yaml' - end - end - context 'when config is defined in a custom path in the repository' do let(:ci_config_path) { 'path/to/config.yml' } let(:config_content_result) do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 07439880beb..a45ad514da2 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -201,8 +201,6 @@ ci_pipelines: - sourced_pipelines - triggered_by_pipeline - triggered_pipelines -- child_pipelines -- parent_pipeline - downstream_bridges - job_artifacts - vulnerabilities_occurrence_pipelines diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ce01765bb8c..b30e88532e1 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2716,114 +2716,4 @@ describe Ci::Pipeline, :mailer do end end end - - describe '#parent_pipeline' do - let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } - - context 'when pipeline is triggered by a pipeline from the same project' do - let(:upstream_pipeline) { create(:ci_pipeline, project: pipeline.project) } - - before do - create(:ci_sources_pipeline, - source_pipeline: upstream_pipeline, - source_project: project, - pipeline: pipeline, - project: project) - end - - it 'returns the parent pipeline' do - expect(pipeline.parent_pipeline).to eq(upstream_pipeline) - end - - it 'is child' do - expect(pipeline).to be_child - end - end - - context 'when pipeline is triggered by a pipeline from another project' do - let(:upstream_pipeline) { create(:ci_pipeline) } - - before do - create(:ci_sources_pipeline, - source_pipeline: upstream_pipeline, - source_project: upstream_pipeline.project, - pipeline: pipeline, - project: project) - end - - it 'returns nil' do - expect(pipeline.parent_pipeline).to be_nil - end - - it 'is not child' do - expect(pipeline).not_to be_child - end - end - - context 'when pipeline is not triggered by a pipeline' do - it 'returns nil' do - expect(pipeline.parent_pipeline).to be_nil - end - - it 'is not child' do - expect(pipeline).not_to be_child - end - end - end - - describe '#child_pipelines' do - let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } - - context 'when pipeline triggered other pipelines on same project' do - let(:downstream_pipeline) { create(:ci_pipeline, project: pipeline.project) } - - before do - create(:ci_sources_pipeline, - source_pipeline: pipeline, - source_project: pipeline.project, - pipeline: downstream_pipeline, - project: pipeline.project) - end - - it 'returns the child pipelines' do - expect(pipeline.child_pipelines).to eq [downstream_pipeline] - end - - it 'is parent' do - expect(pipeline).to be_parent - end - end - - context 'when pipeline triggered other pipelines on another project' do - let(:downstream_pipeline) { create(:ci_pipeline) } - - before do - create(:ci_sources_pipeline, - source_pipeline: pipeline, - source_project: pipeline.project, - pipeline: downstream_pipeline, - project: downstream_pipeline.project) - end - - it 'returns empty array' do - expect(pipeline.child_pipelines).to be_empty - end - - it 'is not parent' do - expect(pipeline).not_to be_parent - end - end - - context 'when pipeline did not trigger any pipelines' do - it 'returns empty array' do - expect(pipeline.child_pipelines).to be_empty - end - - it 'is not parent' do - expect(pipeline).not_to be_parent - end - end - end end diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index d95aaf3d104..75f3bdfcc9e 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -123,6 +123,26 @@ describe PipelineEntity do end end + context 'delete path' do + context 'user has ability to delete pipeline' do + let(:project) { create(:project, namespace: user.namespace) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + it 'contains delete path' do + expect(subject[:delete_path]).to be_present + end + end + + context 'user does not have ability to delete pipeline' do + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + it 'does not contain delete path' do + expect(subject).not_to have_key(:delete_path) + end + end + end + context 'when pipeline ref is empty' do let(:pipeline) { create(:ci_empty_pipeline) } diff --git a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb deleted file mode 100644 index 33cd6e164b0..00000000000 --- a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -describe Ci::CreatePipelineService do - let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { create(:admin) } - let(:ref) { 'refs/heads/master' } - let(:service) { described_class.new(project, user, { ref: ref }) } - - context 'custom config content' do - let(:bridge) do - double(:bridge, yaml_for_downstream: <<~YML - rspec: - script: rspec - custom: - script: custom - YML - ) - end - - subject { service.execute(:push, bridge: bridge) } - - it 'creates a pipeline using the content passed in as param' do - expect(subject).to be_persisted - expect(subject.builds.map(&:name)).to eq %w[rspec custom] - expect(subject.config_source).to eq 'bridge_source' - end - end -end |