diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-20 06:09:03 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-20 06:09:03 +0000 |
commit | 99551d44588b9c815df9691c8e619eb8beaa0045 (patch) | |
tree | e1244297818b544c6837b2eefc58f1fe288b8b90 | |
parent | fe75b57542f67ae643d42e9ab7f317cedb51df71 (diff) | |
download | gitlab-ce-99551d44588b9c815df9691c8e619eb8beaa0045.tar.gz |
Add latest changes from gitlab-org/gitlab@master
89 files changed, 670 insertions, 610 deletions
diff --git a/app/assets/javascripts/boards/components/board_extra_actions.vue b/app/assets/javascripts/boards/components/board_extra_actions.vue new file mode 100644 index 00000000000..b802ccc7882 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_extra_actions.vue @@ -0,0 +1,57 @@ +<script> +import { GlTooltip, GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + name: 'BoardExtraActions', + components: { + GlTooltip, + GlButton, + }, + props: { + canAdminList: { + type: Boolean, + required: true, + }, + disabled: { + type: Boolean, + required: true, + }, + openModal: { + type: Function, + required: true, + }, + }, + computed: { + tooltipTitle() { + if (this.disabled) { + return __('Please add a list to your board first'); + } + + return ''; + }, + }, +}; +</script> + +<template> + <div class="board-extra-actions"> + <span ref="addIssuesButtonTooltip" class="gl-ml-3"> + <gl-button + v-if="canAdminList" + type="button" + data-placement="bottom" + data-track-event="click_button" + data-track-label="board_add_issues" + :disabled="disabled" + :aria-disabled="disabled" + @click="openModal" + > + {{ __('Add issues') }} + </gl-button> + </span> + <gl-tooltip v-if="disabled" :target="() => $refs.addIssuesButtonTooltip" placement="bottom"> + {{ tooltipTitle }} + </gl-tooltip> + </div> +</template> diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue index 6533990d07c..392e056dcbf 100644 --- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue @@ -34,14 +34,14 @@ export default { }, }, computed: { - ...mapGetters(['isSidebarOpen']), + ...mapGetters(['isSidebarOpen', 'shouldUseGraphQL']), ...mapState(['activeId', 'sidebarType', 'boardLists']), activeList() { /* Warning: Though a computed property it is not reactive because we are referencing a List Model class. Reactivity only applies to plain JS objects */ - if (this.glFeatures.graphqlBoardLists) { + if (this.shouldUseGraphQL) { return this.boardLists[this.activeId]; } return boardsStore.state.lists.find(({ id }) => id === this.activeId); diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 1f0ac21762d..887abe79059 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -1,6 +1,5 @@ import Vue from 'vue'; import { mapActions, mapState } from 'vuex'; -import { GlTooltip, GlButton } from '@gitlab/ui'; import 'ee_else_ce/boards/models/issue'; import 'ee_else_ce/boards/models/list'; @@ -19,6 +18,7 @@ import { import VueApollo from 'vue-apollo'; import BoardContent from '~/boards/components/board_content.vue'; +import BoardExtraActions from '~/boards/components/board_extra_actions.vue'; import createDefaultClient from '~/lib/graphql'; import { deprecatedCreateFlash as Flash } from '~/flash'; import { __ } from '~/locale'; @@ -299,10 +299,6 @@ export default () => { // eslint-disable-next-line no-new new Vue({ el: issueBoardsModal, - components: { - GlTooltip, - GlButton, - }, mixins: [modalMixin], data() { return { @@ -319,13 +315,6 @@ export default () => { } return !this.store.lists.filter(list => !list.preset).length; }, - tooltipTitle() { - if (this.disabled) { - return __('Please add a list to your board first'); - } - - return ''; - }, }, methods: { openModal() { @@ -334,29 +323,15 @@ export default () => { } }, }, - template: ` - <div class="board-extra-actions"> - <span ref="addIssuesButtonTooltip" class="gl-ml-3"> - <gl-button - type="button" - data-placement="bottom" - data-track-event="click_button" - data-track-label="board_add_issues" - :disabled="disabled" - :aria-disabled="disabled" - v-if="canAdminList" - @click="openModal"> - Add issues - </button> - </span> - <gl-tooltip - v-if="disabled" - :target="() => $refs.addIssuesButtonTooltip" - placement="bottom"> - {{tooltipTitle}} - </gl-tooltip> - </div> - `, + render(createElement) { + return createElement(BoardExtraActions, { + props: { + canAdminList: this.$options.el.hasAttribute('data-can-admin-list'), + openModal: this.openModal, + disabled: this.disabled, + }, + }); + }, }); } diff --git a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue index b4a6286eb55..26b18f9bf5a 100644 --- a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue +++ b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue @@ -15,32 +15,13 @@ export default { FeatureFlagForm, }, mixins: [glFeatureFlagMixin()], - props: { - environmentsEndpoint: { - type: String, - required: true, - }, - projectId: { - type: String, - required: true, - }, - featureFlagIssuesEndpoint: { - type: String, - required: true, - }, - showUserCallout: { - type: Boolean, - required: true, - }, + inject: { + showUserCallout: {}, userCalloutId: { default: '', - type: String, - required: false, }, userCalloutsPath: { default: '', - type: String, - required: false, }, }, data() { @@ -150,13 +131,10 @@ export default { <feature-flag-form :name="name" :description="description" - :project-id="projectId" :scopes="scopes" :strategies="strategies" :cancel-path="path" :submit-text="__('Save changes')" - :environments-endpoint="environmentsEndpoint" - :feature-flag-issues-endpoint="featureFlagIssuesEndpoint" :active="active" :version="version" @handleSubmit="data => updateFeatureFlag(data)" diff --git a/app/assets/javascripts/feature_flags/components/environments_dropdown.vue b/app/assets/javascripts/feature_flags/components/environments_dropdown.vue index 3533771e3ad..3caf536b6a2 100644 --- a/app/assets/javascripts/feature_flags/components/environments_dropdown.vue +++ b/app/assets/javascripts/feature_flags/components/environments_dropdown.vue @@ -32,10 +32,6 @@ export default { GlSearchBoxByType, }, props: { - endpoint: { - type: String, - required: true, - }, value: { type: String, required: false, @@ -57,6 +53,7 @@ export default { required: false, }, }, + inject: ['environmentsEndpoint'], data() { return { environmentSearch: this.value, @@ -82,7 +79,7 @@ export default { this.isLoading = true; this.openSuggestions(); axios - .get(this.endpoint, { params: { query: this.environmentSearch } }) + .get(this.environmentsEndpoint, { params: { query: this.environmentSearch } }) .then(({ data }) => { this.results = data || []; this.isLoading = false; diff --git a/app/assets/javascripts/feature_flags/components/form.vue b/app/assets/javascripts/feature_flags/components/form.vue index 2858f02688f..3c1944d91bd 100644 --- a/app/assets/javascripts/feature_flags/components/form.vue +++ b/app/assets/javascripts/feature_flags/components/form.vue @@ -64,10 +64,6 @@ export default { required: false, default: '', }, - projectId: { - type: String, - required: true, - }, scopes: { type: Array, required: false, @@ -81,15 +77,6 @@ export default { type: String, required: true, }, - environmentsEndpoint: { - type: String, - required: true, - }, - featureFlagIssuesEndpoint: { - type: String, - required: false, - default: '', - }, strategies: { type: Array, required: false, @@ -101,6 +88,12 @@ export default { default: LEGACY_FLAG, }, }, + inject: { + projectId: {}, + featureFlagIssuesEndpoint: { + default: '', + }, + }, translations: { allEnvironmentsText: s__('FeatureFlags|* (All Environments)'), @@ -353,7 +346,6 @@ export default { :key="keyFor(strategy)" :strategy="strategy" :index="index" - :endpoint="environmentsEndpoint" :user-lists="userLists" @change="onFormStrategyChange($event, index)" @delete="deleteStrategy(strategy)" @@ -411,7 +403,6 @@ export default { v-else class="col-12" :value="scope.environmentScope" - :endpoint="environmentsEndpoint" :disabled="!canUpdateScope(scope) || scope.environmentScope !== ''" @selectEnvironment="env => (scope.environmentScope = env)" @createClicked="env => (scope.environmentScope = env)" @@ -547,7 +538,6 @@ export default { <div class="table-mobile-content js-feature-flag-status"> <environments-dropdown class="js-new-scope-name col-12" - :endpoint="environmentsEndpoint" :value="newScope" @selectEnvironment="env => createNewScope({ environmentScope: env })" @createClicked="env => createNewScope({ environmentScope: env })" diff --git a/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue b/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue index f1371c0320d..f2017c22abf 100644 --- a/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue +++ b/app/assets/javascripts/feature_flags/components/new_environments_dropdown.vue @@ -21,12 +21,7 @@ export default { GlIcon, GlLoadingIcon, }, - props: { - endpoint: { - type: String, - required: true, - }, - }, + inject: ['environmentsEndpoint'], data() { return { environmentSearch: '', @@ -52,7 +47,7 @@ export default { fetchEnvironments: debounce(function debouncedFetchEnvironments() { this.isLoading = true; axios - .get(this.endpoint, { params: { query: this.environmentSearch } }) + .get(this.environmentsEndpoint, { params: { query: this.environmentSearch } }) .then(({ data }) => { this.results = data || []; }) diff --git a/app/assets/javascripts/feature_flags/components/new_feature_flag.vue b/app/assets/javascripts/feature_flags/components/new_feature_flag.vue index 927265b83a1..9472eddf336 100644 --- a/app/assets/javascripts/feature_flags/components/new_feature_flag.vue +++ b/app/assets/javascripts/feature_flags/components/new_feature_flag.vue @@ -19,28 +19,13 @@ export default { FeatureFlagForm, }, mixins: [featureFlagsMixin()], - props: { - environmentsEndpoint: { - type: String, - required: true, - }, - projectId: { - type: String, - required: true, - }, - showUserCallout: { - type: Boolean, - required: true, - }, + inject: { + showUserCallout: {}, userCalloutId: { default: '', - type: String, - required: false, }, userCalloutsPath: { default: '', - type: String, - required: false, }, }, data() { @@ -105,12 +90,10 @@ export default { </div> <feature-flag-form - :project-id="projectId" :cancel-path="path" :submit-text="s__('FeatureFlags|Create feature flag')" :scopes="scopes" :strategies="strategies" - :environments-endpoint="environmentsEndpoint" :version="version" @handleSubmit="data => createFeatureFlag(data)" /> diff --git a/app/assets/javascripts/feature_flags/components/strategy.vue b/app/assets/javascripts/feature_flags/components/strategy.vue index 6ef7de62386..9c41dde62e4 100644 --- a/app/assets/javascripts/feature_flags/components/strategy.vue +++ b/app/assets/javascripts/feature_flags/components/strategy.vue @@ -41,11 +41,6 @@ export default { type: Number, required: true, }, - endpoint: { - type: String, - required: false, - default: '', - }, userLists: { type: Array, required: false, @@ -182,7 +177,6 @@ export default { > <new-environments-dropdown :id="environmentsDropdownId" - :endpoint="endpoint" class="gl-mr-3" @add="addEnvironment" /> diff --git a/app/assets/javascripts/feature_flags/edit.js b/app/assets/javascripts/feature_flags/edit.js index 2e2e2383eb6..b4d2111acf3 100644 --- a/app/assets/javascripts/feature_flags/edit.js +++ b/app/assets/javascripts/feature_flags/edit.js @@ -13,6 +13,12 @@ export default () => { strategyTypeDocsPagePath, endpoint, featureFlagsPath, + environmentsEndpoint, + projectId, + featureFlagIssuesEndpoint, + userCalloutsPath, + userCalloutId, + showUserCallout, } = el.dataset; return new Vue({ @@ -21,18 +27,15 @@ export default () => { provide: { environmentsScopeDocsPath, strategyTypeDocsPagePath, + environmentsEndpoint, + projectId, + featureFlagIssuesEndpoint, + userCalloutsPath, + userCalloutId, + showUserCallout: parseBoolean(showUserCallout), }, render(createElement) { - return createElement(EditFeatureFlag, { - props: { - environmentsEndpoint: el.dataset.environmentsEndpoint, - projectId: el.dataset.projectId, - featureFlagIssuesEndpoint: el.dataset.featureFlagIssuesEndpoint, - userCalloutsPath: el.dataset.userCalloutsPath, - userCalloutId: el.dataset.userCalloutId, - showUserCallout: parseBoolean(el.dataset.showUserCallout), - }, - }); + return createElement(EditFeatureFlag); }, }); }; diff --git a/app/assets/javascripts/feature_flags/new.js b/app/assets/javascripts/feature_flags/new.js index 8f1436314e0..a1efbd87ec4 100644 --- a/app/assets/javascripts/feature_flags/new.js +++ b/app/assets/javascripts/feature_flags/new.js @@ -13,6 +13,11 @@ export default () => { strategyTypeDocsPagePath, endpoint, featureFlagsPath, + environmentsEndpoint, + projectId, + userCalloutsPath, + userCalloutId, + showUserCallout, } = el.dataset; return new Vue({ @@ -21,17 +26,14 @@ export default () => { provide: { environmentsScopeDocsPath, strategyTypeDocsPagePath, + environmentsEndpoint, + projectId, + userCalloutsPath, + userCalloutId, + showUserCallout: parseBoolean(showUserCallout), }, render(createElement) { - return createElement(NewFeatureFlag, { - props: { - environmentsEndpoint: el.dataset.environmentsEndpoint, - projectId: el.dataset.projectId, - userCalloutsPath: el.dataset.userCalloutsPath, - userCalloutId: el.dataset.userCalloutId, - showUserCallout: parseBoolean(el.dataset.showUserCallout), - }, - }); + return createElement(NewFeatureFlag); }, }); }; diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index 146e818d654..ee292190e06 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -1,10 +1,9 @@ <script> /* eslint-disable @gitlab/vue-require-i18n-strings */ import { mapActions, mapState, mapGetters } from 'vuex'; -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; import IdeStatusList from './ide_status_list.vue'; import IdeStatusMr from './ide_status_mr.vue'; -import tooltip from '~/vue_shared/directives/tooltip'; import timeAgoMixin from '~/vue_shared/mixins/timeago'; import CiIcon from '../../vue_shared/components/ci_icon.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; @@ -19,7 +18,7 @@ export default { IdeStatusMr, }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, mixins: [timeAgoMixin], data() { @@ -85,7 +84,7 @@ export default { @click="openRightPane($options.rightSidebarViews.pipelines)" > <ci-icon - v-tooltip + v-gl-tooltip :status="latestPipeline.details.status" :title="latestPipeline.details.status.text" /> @@ -99,7 +98,7 @@ export default { <gl-icon name="commit" /> <a - v-tooltip + v-gl-tooltip :title="lastCommit.message" :href="getCommitPath(lastCommit.short_id)" class="commit-sha" @@ -116,7 +115,7 @@ export default { /> {{ lastCommit.author_name }} <time - v-tooltip + v-gl-tooltip :datetime="lastCommit.committed_date" :title="tooltipTitle(lastCommit.committed_date)" data-placement="top" diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 7b8193d5bf3..8bbd4300c96 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -4,7 +4,7 @@ import $ from 'jquery'; import { difference, isEqual, escape, sortBy, template, union } from 'lodash'; -import { sprintf, s__, __ } from './locale'; +import { sprintf, __ } from './locale'; import axios from './lib/utils/axios_utils'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; import CreateLabelDropdown from './create_label'; @@ -13,7 +13,6 @@ import ModalStore from './boards/stores/modal_store'; import boardsStore from './boards/stores/boards_store'; import { isScopedLabel } from '~/lib/utils/common_utils'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; -import { fixTitle } from '~/tooltips'; export default class LabelsSelect { constructor(els, options = {}) { @@ -44,7 +43,6 @@ export default class LabelsSelect { const $block = $selectbox.closest('.block'); const $form = $dropdown.closest('form, .js-issuable-update'); const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span'); - const $sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip'); const $value = $block.find('.value'); const $dropdownMenu = $dropdown.parent().find('.dropdown-menu'); // eslint-disable-next-line no-jquery/no-fade @@ -91,7 +89,6 @@ export default class LabelsSelect { axios .put(issueUpdateURL, data) .then(({ data }) => { - let labelTooltipTitle; let template; // eslint-disable-next-line no-jquery/no-fade $loading.fadeOut(); @@ -151,24 +148,6 @@ export default class LabelsSelect { $value.removeAttr('style').html(template); $sidebarCollapsedValue.text(labelCount); - if (data.labels.length) { - let labelTitles = data.labels.map(label => label.title); - - if (labelTitles.length > 5) { - labelTitles = labelTitles.slice(0, 5); - labelTitles.push( - sprintf(s__('Labels|and %{count} more'), { count: data.labels.length - 5 }), - ); - } - - labelTooltipTitle = labelTitles.join(', '); - } else { - labelTooltipTitle = __('Labels'); - } - - $sidebarLabelTooltip.attr('title', labelTooltipTitle); - fixTitle($sidebarLabelTooltip); - $('.has-tooltip', $value).tooltip({ container: 'body', }); diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index c912f9bfd3c..a31cb0b0485 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -44,4 +44,3 @@ @import './pages/tree'; @import './pages/trials'; @import './pages/users'; -@import './pages/wiki'; diff --git a/app/assets/stylesheets/page_bundles/terminal.scss b/app/assets/stylesheets/page_bundles/terminal.scss new file mode 100644 index 00000000000..627baf96d6f --- /dev/null +++ b/app/assets/stylesheets/page_bundles/terminal.scss @@ -0,0 +1,3 @@ +#terminal > div { + min-height: 450px; +} diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/page_bundles/wiki.scss index ccf11058b5b..eb34e7f3876 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/page_bundles/wiki.scss @@ -1,3 +1,5 @@ +@import 'mixins_and_variables_and_functions'; + .title .edit-wiki-header { width: 780px; margin-left: auto; @@ -9,7 +11,7 @@ position: relative; .wiki-breadcrumb { - border-bottom: 1px solid $white-normal; + border-bottom: 1px solid var(--gray-50, $gray-50); padding: 11px 0; } @@ -20,16 +22,16 @@ .wiki-last-edit-by { display: block; - color: $gl-text-color-secondary; + color: var(--gray-500, $gray-500); strong { - color: $gl-text-color; + color: var(--gl-text-color, $gl-text-color); } } .light { font-weight: $gl-font-weight-normal; - color: $gl-text-color-secondary; + color: var(--gray-500, $gray-500); } .git-clone-holder { @@ -92,7 +94,7 @@ } a { - color: $layout-link-gray; + color: var(--gray-400, $gray-400); &:hover, &.active { @@ -105,7 +107,7 @@ } .active > a { - color: $black; + color: var(--black, $black); } ul.wiki-pages, @@ -134,7 +136,7 @@ ul.wiki-pages-list.content-list { a { - color: $blue-600; + color: var(--blue-600, $blue-600); } ul { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9a2e7565882..2df43b861b2 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -89,26 +89,6 @@ } } -/** - * Terminal - */ -[data-page='projects:jobs:terminal'], -[data-page='projects:environments:terminal'] { - .terminal-container { - .content-block { - border-bottom: 0; - } - - #terminal { - margin-top: 10px; - - > div { - min-height: 450px; - } - } - } -} - .pipelines-container .top-area .nav-controls > .btn:last-child { float: none; } diff --git a/app/assets/stylesheets/themes/_dark.scss b/app/assets/stylesheets/themes/_dark.scss index cd607e9b247..66cc4452858 100644 --- a/app/assets/stylesheets/themes/_dark.scss +++ b/app/assets/stylesheets/themes/_dark.scss @@ -165,6 +165,7 @@ body.gl-dark { --border-color: #{$border-color}; --white: #{$white}; + --black: #{$black}; } $border-white-light: $gray-900; diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index 3f5f3b6e9df..0d7af57328a 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -150,7 +150,7 @@ module IssuableCollections common_attributes + [:project, project: :namespace] when 'MergeRequest' common_attributes + [ - :target_project, :latest_merge_request_diff, :approvals, :approved_by_users, :reviewers, + :target_project, :latest_merge_request_diff, :approvals, :approved_by_users, source_project: :route, head_pipeline: :project, target_project: :namespace ] end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e0321b0b657..9a8965dbeb6 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -44,7 +44,6 @@ class Projects::IssuesController < Projects::ApplicationController push_frontend_feature_flag(:vue_issuable_sidebar, project.group) push_frontend_feature_flag(:tribute_autocomplete, @project) push_frontend_feature_flag(:vue_issuables_list, project) - push_frontend_feature_flag(:vue_sidebar_labels, @project, default_enabled: true) end before_action only: :show do diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 0920a27fcfb..2963321f803 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -76,9 +76,9 @@ module Projects [ :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_human_readable, :build_coverage_regex, :public_builds, - :auto_cancel_pending_pipelines, :forward_deployment_enabled, :ci_config_path, + :auto_cancel_pending_pipelines, :ci_config_path, auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy], - ci_cd_settings_attributes: [:default_git_depth] + ci_cd_settings_attributes: [:default_git_depth, :forward_deployment_enabled] ].tap do |list| list << :max_artifacts_size if can?(current_user, :update_max_artifacts_size, project) end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index d543267bd44..9c4aecedd93 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -399,7 +399,7 @@ class IssuableFinder elsif params.filter_by_any_assignee? items.assigned elsif params.assignee - items_assigned_to(items, params.assignee) + items.assigned_to(params.assignee) elsif params.assignee_id? || params.assignee_username? # assignee not found items.none else @@ -407,10 +407,6 @@ class IssuableFinder end end - def items_assigned_to(items, user) - items.assigned_to(user) - end - def by_negated_assignee(items) # We want CE users to be able to say "Issues not assigned to either PersonA nor PersonB" if not_params.assignees.present? diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 9669d4acf2d..c998de75ab2 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -164,10 +164,6 @@ class MergeRequestsFinder < IssuableFinder end # rubocop: enable CodeReuse/Finder - def items_assigned_to(items, user) - MergeRequest.from_union([super, items.reviewer_assigned_to(user)]) - end - def by_deployments(items) # Until this feature flag is enabled permanently, we retain the old # filtering behaviour/code. diff --git a/app/graphql/mutations/issues/move.rb b/app/graphql/mutations/issues/move.rb new file mode 100644 index 00000000000..e6971c9df8c --- /dev/null +++ b/app/graphql/mutations/issues/move.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Mutations + module Issues + class Move < Base + graphql_name 'IssueMove' + + argument :target_project_path, + GraphQL::ID_TYPE, + required: true, + description: 'The project to move the issue to' + + def resolve(project_path:, iid:, target_project_path:) + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/267762') + + issue = authorized_find!(project_path: project_path, iid: iid) + source_project = issue.project + target_project = resolve_project(full_path: target_project_path).sync + + begin + moved_issue = ::Issues::MoveService.new(source_project, current_user).execute(issue, target_project) + rescue ::Issues::MoveService::MoveError => error + errors = error.message + end + + { + issue: moved_issue, + errors: Array.wrap(errors) + } + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index de60279a71f..3f48e7b4a16 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -31,6 +31,7 @@ module Types mount_mutation Mutations::Issues::SetSeverity mount_mutation Mutations::Issues::SetSubscription mount_mutation Mutations::Issues::Update + mount_mutation Mutations::Issues::Move mount_mutation Mutations::MergeRequests::Create mount_mutation Mutations::MergeRequests::Update mount_mutation Mutations::MergeRequests::SetLabels diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 70ffbf3b1dd..f8e7711959a 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -49,12 +49,6 @@ module IssuablesHelper "#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})" end - def sidebar_label_filter_path(base_path, label_name) - query_params = { label_name: [label_name] }.to_query - - "#{base_path}?#{query_params}" - end - def multi_label_name(current_labels, default_label) return default_label if current_labels.blank? @@ -228,19 +222,6 @@ module IssuablesHelper nil end - def issuable_labels_tooltip(labels, limit: 5) - first, last = labels.partition.with_index { |_, i| i < limit } - - if labels && labels.any? - label_names = first.collect { |label| label.fetch(:title) } - label_names << "and #{last.size} more" unless last.empty? - - label_names.join(', ') - else - _("Labels") - end - end - def issuables_state_counter_text(issuable_type, state, display_count) titles = { opened: "Open" diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 436af08ce01..312d535a92c 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -241,25 +241,6 @@ module LabelsHelper }.merge(opts) end - def sidebar_label_dropdown_data(issuable_type, issuable_sidebar) - label_dropdown_data(nil, { - default_label: "Labels", - field_name: "#{issuable_type}[label_names][]", - ability_name: issuable_type, - namespace_path: issuable_sidebar[:namespace_path], - project_path: issuable_sidebar[:project_path], - issue_update: issuable_sidebar[:issuable_json_path], - labels: issuable_sidebar[:project_labels_path], - display: 'static' - }) - end - - def label_from_hash(hash) - klass = hash[:group_id] ? GroupLabel : ProjectLabel - - klass.new(hash.slice(:color, :description, :title, :group_id, :project_id)) - end - def issuable_types ['issues', 'merge requests'] end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 381ba9d27d3..2d0d98136ec 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -70,7 +70,7 @@ class Deployment < ApplicationRecord end after_transition any => :running do |deployment| - next unless deployment.project.forward_deployment_enabled? + next unless deployment.project.ci_forward_deployment_enabled? deployment.run_after_commit do Deployments::DropOlderDeploymentsWorker.perform_async(id) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a9bccb02689..24541ba3218 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -302,10 +302,6 @@ class MergeRequest < ApplicationRecord includes(:metrics) end - scope :reviewer_assigned_to, ->(user) do - where("EXISTS (SELECT TRUE FROM merge_request_reviewers WHERE user_id = ? AND merge_request_id = merge_requests.id)", user.id) - end - after_save :keep_around_commit, unless: :importing? alias_attribute :project, :target_project @@ -1695,7 +1691,7 @@ class MergeRequest < ApplicationRecord end def allows_reviewers? - Feature.enabled?(:merge_request_reviewers, project, default_enabled: true) + Feature.enabled?(:merge_request_reviewers, project) end def allows_multiple_reviewers? diff --git a/app/models/project.rb b/app/models/project.rb index 5b1cdf0a5b4..dbedd6d120c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -400,7 +400,7 @@ class Project < ApplicationRecord delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true delegate :dashboard_timezone, to: :metrics_setting, allow_nil: true, prefix: true delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci - delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings + delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings, prefix: :ci delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?, :allow_merge_on_skipped_pipeline=, :has_confluence?, diff --git a/app/views/projects/confluences/show.html.haml b/app/views/projects/confluences/show.html.haml index b87780db4cd..5814b7a00f5 100644 --- a/app/views/projects/confluences/show.html.haml +++ b/app/views/projects/confluences/show.html.haml @@ -1,5 +1,6 @@ - breadcrumb_title _('Confluence') - page_title _('Confluence') +- add_page_specific_style 'page_bundles/wiki' = render layout: 'shared/empty_states/wikis_layout', locals: { image_path: 'illustrations/wiki_login_empty.svg' } do %h4 = s_('WikiEmpty|Confluence is enabled') diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index 3a705d736f3..ed0bc0680d7 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -1,5 +1,5 @@ - page_title _("Terminal for environment"), @environment.name - +- add_page_specific_style 'page_bundles/terminal' - content_for :page_specific_javascripts do = stylesheet_link_tag "xterm.css" @@ -18,4 +18,4 @@ = render 'projects/deployments/actions', deployment: @environment.last_deployment .terminal-container{ class: container_class } - #terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } } + #terminal.gl-mt-4{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } } diff --git a/app/views/projects/jobs/terminal.html.haml b/app/views/projects/jobs/terminal.html.haml index 01f40543926..95acbcae6d9 100644 --- a/app/views/projects/jobs/terminal.html.haml +++ b/app/views/projects/jobs/terminal.html.haml @@ -2,9 +2,10 @@ - add_to_breadcrumbs "##{@build.id}", project_job_path(@project, @build) - breadcrumb_title _('Terminal') - page_title _('Terminal'), "#{@build.name} (##{@build.id})", _('Jobs') - +- add_page_specific_style 'page_bundles/terminal' - content_for :page_specific_javascripts do = stylesheet_link_tag "xterm.css" + .terminal-container - #terminal{ data: { project_path: terminal_project_job_path(@project, @build, format: :ws) } } + #terminal.gl-mt-4{ data: { project_path: terminal_project_job_path(@project, @build, format: :ws) } } diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 5c1b42bb0c1..092055a5f85 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -55,7 +55,7 @@ - if merge_request.assignees.any? %li.gl-display-flex.gl-align-items-center = render 'shared/issuable/assignees', project: merge_request.project, issuable: merge_request - - if Feature.enabled?(:merge_request_reviewers, @project, default_enabled: true) && merge_request.reviewers.any? + - if Feature.enabled?(:merge_request_reviewers, @project) && merge_request.reviewers.any? %li.gl-display-flex.issuable-reviewers = render 'shared/issuable/reviewers', project: merge_request.project, issuable: merge_request = render 'projects/merge_requests/approvals_count', merge_request: merge_request diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index 414a5f264bd..4793e685163 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -90,12 +90,13 @@ .form-group .form-check - = f.check_box :forward_deployment_enabled, { class: 'form-check-input' } - = f.label :forward_deployment_enabled, class: 'form-check-label' do - %strong= _("Skip outdated deployment jobs") - .form-text.text-muted - = _("When a deployment job is successful, skip older deployment jobs that are still pending") - = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'skip-outdated-deployment-jobs'), target: '_blank' + = f.fields_for :ci_cd_settings_attributes, @project.ci_cd_settings do |form| + = form.check_box :forward_deployment_enabled, { class: 'form-check-input' } + = form.label :forward_deployment_enabled, class: 'form-check-label' do + %strong= _("Skip outdated deployment jobs") + .form-text.text-muted + = _("When a deployment job is successful, skip older deployment jobs that are still pending") + = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'skip-outdated-deployment-jobs'), target: '_blank' %hr .form-group diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index 1db4554541d..c166642bae2 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,5 +1,6 @@ - @content_class = "limit-container-width" unless fluid_layout - page_title s_("WikiClone|Git Access"), _("Wiki") +- add_page_specific_style 'page_bundles/wiki' .wiki-page-header.top-area.has-sidebar-toggle.py-3.flex-column.flex-lg-row = wiki_sidebar_toggle_button diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 42da9db4b09..458703ebc5f 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -25,7 +25,7 @@ .block.assignee.qa-assignee-block = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in - - if Feature.enabled?(:merge_request_reviewers, @project, default_enabled: true) && reviewers + - if Feature.enabled?(:merge_request_reviewers, @project) && reviewers .block.reviewer.qa-reviewer-block = render "shared/issuable/sidebar_reviewers", issuable_sidebar: issuable_sidebar, reviewers: reviewers, signed_in: signed_in @@ -103,49 +103,17 @@ .js-due-date-calendar - - if Feature.enabled?(:vue_sidebar_labels, @project, default_enabled: true) - .js-sidebar-labels{ data: { allow_label_create: issuable_sidebar.dig(:current_user, :can_admin_label).to_s, - allow_scoped_labels: issuable_sidebar[:scoped_labels_available].to_s, - can_edit: can_edit_issuable.to_s, - iid: issuable_sidebar[:iid], - issuable_type: issuable_type, - labels_fetch_path: issuable_sidebar[:project_labels_path], - labels_manage_path: project_labels_path(@project), - labels_update_path: issuable_sidebar[:issuable_json_path], - project_issues_path: issuable_sidebar[:project_issuables_path], - project_path: @project.full_path, - selected_labels: issuable_sidebar[:labels].to_json } } - - else - - selected_labels = issuable_sidebar[:labels] - .block.labels{ data: { qa_selector: 'labels_block' } } - .sidebar-collapsed-icon.has-tooltip.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(selected_labels), data: { placement: "left", container: "body", boundary: 'viewport' } } - = sprite_icon('labels') - %span - = selected_labels.size - .title.hide-collapsed - = _('Labels') - = loading_icon(css_class: 'gl-vertical-align-text-bottom hidden block-loading') - - if can_edit_issuable - = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: "labels_edit_button", track_label: "right_sidebar", track_property: "labels", track_event: "click_edit_button", track_value: "" } - .value.issuable-show-labels.dont-hide.hide-collapsed{ class: ("has-labels" if selected_labels.any?) } - - if selected_labels.any? - - selected_labels.each do |label_hash| - = render_label(label_from_hash(label_hash).present(issuable_subject: nil), link: sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label_hash[:title]), dataset: { qa_selector: 'selected_label_content', qa_label_name: label_hash[:title] }) - - else - %span.no-value - = _('None') - .selectbox.hide-collapsed - - selected_labels.each do |label| - = hidden_field_tag "#{issuable_type}[label_names][]", label[:id], id: nil - .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: sidebar_label_dropdown_data(issuable_type, issuable_sidebar) } - %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } - = multi_label_name(selected_labels, "Labels") - = icon('chevron-down', 'aria-hidden': 'true') - .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height{ data: { qa_selector: "labels_dropdown_content"} } - = render partial: "shared/issuable/label_page_default" - - if issuable_sidebar.dig(:current_user, :can_admin_label) - = render partial: "shared/issuable/label_page_create" + .js-sidebar-labels{ data: { allow_label_create: issuable_sidebar.dig(:current_user, :can_admin_label).to_s, + allow_scoped_labels: issuable_sidebar[:scoped_labels_available].to_s, + can_edit: can_edit_issuable.to_s, + iid: issuable_sidebar[:iid], + issuable_type: issuable_type, + labels_fetch_path: issuable_sidebar[:project_labels_path], + labels_manage_path: project_labels_path(@project), + labels_update_path: issuable_sidebar[:issuable_json_path], + project_issues_path: issuable_sidebar[:project_issuables_path], + project_path: @project.full_path, + selected_labels: issuable_sidebar[:labels].to_json } } = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar diff --git a/app/views/shared/wikis/diff.html.haml b/app/views/shared/wikis/diff.html.haml index ff6a4eb3bb7..68bbbd66f4a 100644 --- a/app/views/shared/wikis/diff.html.haml +++ b/app/views/shared/wikis/diff.html.haml @@ -1,4 +1,5 @@ - wiki_page_title @page, _('Changes') +- add_page_specific_style 'page_bundles/wiki' - commit = @diffs.diffable .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row diff --git a/app/views/shared/wikis/edit.html.haml b/app/views/shared/wikis/edit.html.haml index c7d1742da8a..834749caaba 100644 --- a/app/views/shared/wikis/edit.html.haml +++ b/app/views/shared/wikis/edit.html.haml @@ -1,4 +1,5 @@ - wiki_page_title @page, @page.persisted? ? _('Edit') : _('New') +- add_page_specific_style 'page_bundles/wiki' = wiki_page_errors(@error) diff --git a/app/views/shared/wikis/empty.html.haml b/app/views/shared/wikis/empty.html.haml index 62fa6e1907b..c52ead74b4c 100644 --- a/app/views/shared/wikis/empty.html.haml +++ b/app/views/shared/wikis/empty.html.haml @@ -1,4 +1,5 @@ - page_title _("Wiki") - @right_sidebar = false +- add_page_specific_style 'page_bundles/wiki' = render 'shared/empty_states/wikis' diff --git a/app/views/shared/wikis/history.html.haml b/app/views/shared/wikis/history.html.haml index f9d21c8fb57..50ccfdeabd5 100644 --- a/app/views/shared/wikis/history.html.haml +++ b/app/views/shared/wikis/history.html.haml @@ -1,4 +1,5 @@ - wiki_page_title @page, _('History') +- add_page_specific_style 'page_bundles/wiki' .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row = wiki_sidebar_toggle_button diff --git a/app/views/shared/wikis/pages.html.haml b/app/views/shared/wikis/pages.html.haml index ef99d0aabed..76fc9510740 100644 --- a/app/views/shared/wikis/pages.html.haml +++ b/app/views/shared/wikis/pages.html.haml @@ -2,6 +2,7 @@ - breadcrumb_title s_("Wiki|Pages") - page_title s_("Wiki|Pages"), _("Wiki") - sort_title = wiki_sort_title(params[:sort]) +- add_page_specific_style 'page_bundles/wiki' .wiki-page-header.top-area.flex-column.flex-lg-row diff --git a/app/views/shared/wikis/show.html.haml b/app/views/shared/wikis/show.html.haml index 1c83ebee450..6f1c1a3a801 100644 --- a/app/views/shared/wikis/show.html.haml +++ b/app/views/shared/wikis/show.html.haml @@ -1,4 +1,5 @@ - wiki_page_title @page +- add_page_specific_style 'page_bundles/wiki' .wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row = wiki_sidebar_toggle_button diff --git a/changelogs/unreleased/forward_deployment_enabled-api.yml b/changelogs/unreleased/forward_deployment_enabled-api.yml new file mode 100644 index 00000000000..5da83dd8036 --- /dev/null +++ b/changelogs/unreleased/forward_deployment_enabled-api.yml @@ -0,0 +1,5 @@ +--- +title: Support ci_forward_deployment_enabled in edit API +merge_request: 44510 +author: +type: added diff --git a/changelogs/unreleased/issue-233479-Allow_move_issues_on_graphql.yml b/changelogs/unreleased/issue-233479-Allow_move_issues_on_graphql.yml new file mode 100644 index 00000000000..21def475bb2 --- /dev/null +++ b/changelogs/unreleased/issue-233479-Allow_move_issues_on_graphql.yml @@ -0,0 +1,5 @@ +--- +title: Allow to move issues between projects on GraphQL +merge_request: 44491 +author: +type: added diff --git a/config/application.rb b/config/application.rb index 3934afc11fe..75befc8a248 100644 --- a/config/application.rb +++ b/config/application.rb @@ -191,9 +191,11 @@ module Gitlab config.assets.precompile << "page_bundles/pipeline.css" config.assets.precompile << "page_bundles/pipelines.css" config.assets.precompile << "page_bundles/productivity_analytics.css" + config.assets.precompile << "page_bundles/terminal.css" config.assets.precompile << "page_bundles/todos.css" config.assets.precompile << "page_bundles/reports.css" config.assets.precompile << "page_bundles/xterm.css" + config.assets.precompile << "page_bundles/wiki.css" config.assets.precompile << "lazy_bundles/cropper.css" config.assets.precompile << "performance_bar.css" config.assets.precompile << "lib/ace.js" diff --git a/config/feature_flags/development/merge_request_reviewers.yml b/config/feature_flags/development/merge_request_reviewers.yml index 3ee4c1547aa..2180662b9df 100644 --- a/config/feature_flags/development/merge_request_reviewers.yml +++ b/config/feature_flags/development/merge_request_reviewers.yml @@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40488 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/245190 group: group::source code type: development -default_enabled: true +default_enabled: false diff --git a/config/feature_flags/development/vue_sidebar_labels.yml b/config/feature_flags/development/vue_sidebar_labels.yml deleted file mode 100644 index 4dbfa7aca73..00000000000 --- a/config/feature_flags/development/vue_sidebar_labels.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: vue_sidebar_labels -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41561 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/263440 -group: group::project management -type: development -default_enabled: true diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index 16e149999fd..56b7f01e1ad 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -180,7 +180,7 @@ docker login gitlab.example.com:5050 ### Configure Container Registry under its own domain When the Registry is configured to use its own domain, you need a TLS -certificate for that specific domain (for example, `registry.example.com`). You might need +certificate for that specific domain (for example, `registry.example.com`). You might need a wildcard certificate if hosted under a subdomain of your existing GitLab domain, for example, `registry.gitlab.example.com`. diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index effe16fc41a..f5a7b9839b1 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -9257,6 +9257,31 @@ Identifier of Issue scalar IssueID """ +Autogenerated input type of IssueMove +""" +input IssueMoveInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + The IID of the issue to mutate + """ + iid: String! + + """ + The project the issue to mutate is in + """ + projectPath: ID! + + """ + The project to move the issue to + """ + targetProjectPath: ID! +} + +""" Autogenerated input type of IssueMoveList """ input IssueMoveListInput { @@ -9327,6 +9352,26 @@ type IssueMoveListPayload { } """ +Autogenerated return type of IssueMove +""" +type IssueMovePayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Errors encountered during execution of the mutation. + """ + errors: [String!]! + + """ + The issue after mutation + """ + issue: Issue +} + +""" Check permissions for the current user on a issue """ type IssuePermissions { @@ -12179,6 +12224,7 @@ type Mutation { epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload + issueMove(input: IssueMoveInput!): IssueMovePayload issueMoveList(input: IssueMoveListInput!): IssueMoveListPayload issueSetAssignees(input: IssueSetAssigneesInput!): IssueSetAssigneesPayload issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index 408b7e13c15..8edd30c0172 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -25225,6 +25225,69 @@ }, { "kind": "INPUT_OBJECT", + "name": "IssueMoveInput", + "description": "Autogenerated input type of IssueMove", + "fields": null, + "inputFields": [ + { + "name": "projectPath", + "description": "The project the issue to mutate is in", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "iid", + "description": "The IID of the issue to mutate", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "targetProjectPath", + "description": "The project to move the issue to", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", "name": "IssueMoveListInput", "description": "Autogenerated input type of IssueMoveList", "fields": null, @@ -25405,6 +25468,73 @@ }, { "kind": "OBJECT", + "name": "IssueMovePayload", + "description": "Autogenerated return type of IssueMove", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [ + + ], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "errors", + "description": "Errors encountered during execution of the mutation.", + "args": [ + + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "issue", + "description": "The issue after mutation", + "args": [ + + ], + "type": { + "kind": "OBJECT", + "name": "Issue", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + + ], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", "name": "IssuePermissions", "description": "Check permissions for the current user on a issue", "fields": [ @@ -34620,6 +34750,33 @@ "deprecationReason": null }, { + "name": "issueMove", + "description": null, + "args": [ + { + "name": "input", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "IssueMoveInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "IssueMovePayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "issueMoveList", "description": null, "args": [ diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index c6eb8603da9..dca00fc1286 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1308,6 +1308,16 @@ Autogenerated return type of IssueMoveList. | `errors` | String! => Array | Errors encountered during execution of the mutation. | | `issue` | Issue | The issue after mutation | +### IssueMovePayload + +Autogenerated return type of IssueMove. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `clientMutationId` | String | A unique identifier for the client performing the mutation. | +| `errors` | String! => Array | Errors encountered during execution of the mutation. | +| `issue` | Issue | The issue after mutation | + ### IssuePermissions Check permissions for the current user on a issue. diff --git a/doc/api/groups.md b/doc/api/groups.md index 462972206d9..53c92cf85ec 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -440,6 +440,7 @@ Example response: "import_status":"failed", "open_issues_count":10, "ci_default_git_depth":50, + "ci_forward_deployment_enabled":true, "public_jobs":true, "build_timeout":3600, "auto_cancel_pending_pipelines":"enabled", diff --git a/doc/api/job_artifacts.md b/doc/api/job_artifacts.md index d6971484edc..f5510f6ee91 100644 --- a/doc/api/job_artifacts.md +++ b/doc/api/job_artifacts.md @@ -64,7 +64,7 @@ is the same as [getting the job's artifacts](#get-job-artifacts), but by defining the job's name instead of its ID. NOTE: **Note:** -If a pipeline is [parent of other child pipelines](../ci/parent_child_pipelines.md), artifacts +If a pipeline is [parent of other child pipelines](../ci/parent_child_pipelines.md), artifacts are searched in hierarchical order from parent to child. For example, if both parent and child pipelines have a job with the same name, the artifact from the parent pipeline will be returned. diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 072a8c31705..194f48c6e84 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -1007,7 +1007,7 @@ order for it to take effect: value of zero disables approvals for that project. 1. The provided value of `approvals_before_merge` must be greater than the target project's `approvals_before_merge`. -1. This API returns 201 (created) for a successful response. +1. This API returns 201 (created) for a successful response. ```json { diff --git a/doc/api/projects.md b/doc/api/projects.md index b1307e673db..f6ed905cda1 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -156,6 +156,7 @@ When the user is authenticated and `simple` is not set this returns something li "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "ci_default_git_depth": 50, + "ci_forward_deployment_enabled": true, "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, @@ -248,6 +249,7 @@ When the user is authenticated and `simple` is not set this returns something li "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "ci_default_git_depth": 0, + "ci_forward_deployment_enabled": true, "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, @@ -410,6 +412,7 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "ci_default_git_depth": 50, + "ci_forward_deployment_enabled": true, "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, @@ -502,6 +505,7 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo "star_count": 0, "runners_token": "b8547b1dc37721d05889db52fa2f02", "ci_default_git_depth": 0, + "ci_forward_deployment_enabled": true, "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, @@ -856,6 +860,7 @@ GET /projects/:id "star_count": 0, "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "ci_default_git_depth": 50, + "ci_forward_deployment_enabled": true, "public_jobs": true, "shared_with_groups": [ { @@ -1218,6 +1223,7 @@ PUT /projects/:id | `build_coverage_regex` | string | no | Test coverage parsing | | `ci_config_path` | string | no | The path to CI configuration file | | `ci_default_git_depth` | integer | no | Default number of revisions for [shallow cloning](../ci/pipelines/settings.md#git-shallow-clone) | +| `ci_forward_deployment_enabled` | boolean | no | When a new deployment job starts, [skip older deployment jobs](../ci/pipelines/settings.md#skip-outdated-deployment-jobs) that are still pending | | `auto_devops_enabled` | boolean | no | Enable Auto DevOps for this project | | `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) | | `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins | @@ -1701,6 +1707,7 @@ Example response: "star_count": 0, "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "ci_default_git_depth": 50, + "ci_forward_deployment_enabled": true, "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, @@ -1811,6 +1818,7 @@ Example response: "star_count": 0, "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "ci_default_git_depth": 50, + "ci_forward_deployment_enabled": true, "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, diff --git a/doc/api/snippets.md b/doc/api/snippets.md index aab9394e888..431d745ac84 100644 --- a/doc/api/snippets.md +++ b/doc/api/snippets.md @@ -222,7 +222,7 @@ curl --request POST "https://gitlab.example.com/api/v4/snippets" \ ```json { - "title": "This is a snippet", + "title": "This is a snippet", "description": "Hello World snippet", "visibility": "internal", "files": [ @@ -310,10 +310,10 @@ curl --request PUT "https://gitlab.example.com/api/v4/snippets/1" \ ```json { - "title": "foo", + "title": "foo", "files": [ { - "action": "move", + "action": "move", "previous_path": "test.txt", "file_path": "renamed.md" } diff --git a/doc/ci/cloud_deployment/index.md b/doc/ci/cloud_deployment/index.md index b98e939e080..af7df0e1153 100644 --- a/doc/ci/cloud_deployment/index.md +++ b/doc/ci/cloud_deployment/index.md @@ -9,7 +9,7 @@ type: howto Interacting with a major cloud provider may have become a much needed task that's part of your delivery process. With GitLab you can -[deploy your application anywhere](https://about.gitlab.com/stages-devops-lifecycle/deploy-targets/). +[deploy your application anywhere](https://about.gitlab.com/stages-devops-lifecycle/deploy-targets/). For some specific deployment targets, GitLab makes this process less painful by providing Docker images that come with the needed libraries and tools pre-installed. diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md index 7eb387d51b3..378adcd35e9 100644 --- a/doc/ci/multi_project_pipelines.md +++ b/doc/ci/multi_project_pipelines.md @@ -225,7 +225,7 @@ In the upstream pipeline: 1. Save the variables in a `.env` file. 1. Save the `.env` file as a `dotenv` report. -1. Trigger the downstream pipeline. +1. Trigger the downstream pipeline. ```yaml build_vars: diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 5334e3e3368..2e4ab68a0e8 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -2613,7 +2613,7 @@ The `stop_review_app` job is **required** to have the following keywords defined - `environment:action` Additionally, both jobs should have matching [`rules`](../yaml/README.md#onlyexcept-basic) -or [`only/except`](../yaml/README.md#onlyexcept-basic) configuration. +or [`only/except`](../yaml/README.md#onlyexcept-basic) configuration. In the example above, if the configuration is not identical: @@ -4409,7 +4409,7 @@ You can use the `$CI_COMMIT_REF_SLUG` variable to specify your [`cache:key`](#ca For example, if your `$CI_COMMIT_REF_SLUG` is `test` you can set a job to download cache that's tagged with `test`. -If a cache with this tag is not found, you can use `CACHE_FALLBACK_KEY` to +If a cache with this tag is not found, you can use `CACHE_FALLBACK_KEY` to specify a cache to use when none exists. For example: diff --git a/doc/development/database/setting_multiple_values.md b/doc/development/database/setting_multiple_values.md index 428c04b9a0f..5569a0e10b7 100644 --- a/doc/development/database/setting_multiple_values.md +++ b/doc/development/database/setting_multiple_values.md @@ -22,7 +22,7 @@ with updates(obj_id, new_title, new_weight) as ( values (1 :: integer, 'Very difficult issue' :: text, 8 :: integer), (2, 'Very easy issue', 1) ) -update issues +update issues set title = new_title, weight = new_weight from updates where id = obj_id @@ -88,7 +88,7 @@ objects = Foo.from_union([ # At this point, all the objects are instances of Foo, even the ones from the # Bar table mapping = objects.to_h { |obj| [obj, bazzes[obj.id] } - + # Issues at most 2 queries ::Gitlab::Database::BulkUpdate.execute(%i[baz], mapping) do |obj| obj.object_type.constantize diff --git a/doc/development/integrations/secure.md b/doc/development/integrations/secure.md index b085f1244c1..1094074cab6 100644 --- a/doc/development/integrations/secure.md +++ b/doc/development/integrations/secure.md @@ -384,6 +384,9 @@ reported for the same commit, except for `CWE` and `WASC`. Not all vulnerabilities have CVEs, and a CVE can be identified multiple times. As a result, a CVE isn't a stable identifier and you shouldn't assume it as such when tracking vulnerabilities. +The maximum number of identifiers for a vulnerability is set as 20. If a vulnerability has more than 20 identifiers, +the system will save only the first 20 of them. + ### Location The `location` indicates where the vulnerability has been detected. diff --git a/doc/user/admin_area/approving_users.md b/doc/user/admin_area/approving_users.md index a8537666581..486d0b6a25d 100644 --- a/doc/user/admin_area/approving_users.md +++ b/doc/user/admin_area/approving_users.md @@ -11,7 +11,7 @@ type: howto When [Require admin approval for new sign-ups](settings/sign_up_restrictions.md#require-admin-approval-for-new-sign-ups) is enabled, any user that signs up for an account using the registration form is placed under a **Pending approval** state. -A user pending approval is functionally identical to a [blocked](blocking_unblocking_users.md) user. +A user pending approval is functionally identical to a [blocked](blocking_unblocking_users.md) user. A user pending approval: diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md index b76aa856227..f380b36cc00 100644 --- a/doc/user/group/epics/index.md +++ b/doc/user/group/epics/index.md @@ -177,7 +177,7 @@ You can also consult the [group permissions table](../../permissions.md#group-me Once you write your comment, you can either: -- Click **Comment** to publish your comment. +- Click **Comment** to publish your comment. - Click **Start thread** to start a thread within that epic's discussion. ### Activity sort order diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md index a052b590e92..d4fb662c3a6 100644 --- a/doc/user/packages/composer_repository/index.md +++ b/doc/user/packages/composer_repository/index.md @@ -136,7 +136,7 @@ Install a package from the Package Registry so you can use it as a dependency. Prerequisites: - A package in the Package Registry. -- The group ID, which is on the group's home page. +- The group ID, which is on the group's home page. - A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to, at minimum, `read_api`. NOTE: **Note:** @@ -155,7 +155,7 @@ To install a package: - Set the required package version: - ```shell + ```shell composer require <package_name>:<version> ``` @@ -223,7 +223,7 @@ To install a package: ``` Result in the `composer.json` file: - + ```json { ... @@ -240,7 +240,7 @@ To install a package: }, ... } - ``` + ``` You can unset this with the command: diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index 7a77fa2cf53..22e1a95649d 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -154,7 +154,7 @@ To add the GitLab NuGet Repository as a source for .NET, create a file named `nu When uploading packages, note that: -- The Package Registry on GitLab.com can store up to 500 MB of content. This limit is [configurable for self-managed GitLab instances](../../../administration/instance_limits.md#package-registry-limits). +- The Package Registry on GitLab.com can store up to 500 MB of content. This limit is [configurable for self-managed GitLab instances](../../../administration/instance_limits.md#package-registry-limits). - If you upload the same package with the same version multiple times, each consecutive upload is saved as a separate file. When installing a package, GitLab serves the most recent file. - When uploading packages to GitLab, they are not displayed in the packages UI of your project diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index db1a8d7d5a2..4f0354f86af 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -98,7 +98,7 @@ If you delete a label, it is permanently deleted. All references to the label ar If you previously created a project label and now want to make it available for other projects within the same group, you can promote it to a group label. -If other projects in the same group have a label with the same title, they are all +If other projects in the same group have a label with the same title, they are all merged with the new group label. If a group label with the same title exists, it is also merged. @@ -126,7 +126,7 @@ follow the same process as [creating a project label](#project-labels). #### Create group labels from epics **(ULTIMATE)** -You can create group labels from the epic sidebar. The labels you create +You can create group labels from the epic sidebar. The labels you create belong to the immediate group to which the epic belongs. The process is the same as creating a [project label from an issue or merge request](#project-labels). @@ -160,7 +160,7 @@ title, for example: ![Scoped labels](img/labels_key_value_v13_5.png) An issue, merge request or epic cannot have two scoped labels, of the form `key::value`, -with the same `key`. Adding a new label with the same `key`, but a different `value` +with the same `key`. Adding a new label with the same `key`, but a different `value` causes the previous `key` label to be replaced with the new label. For example: diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index fb599d68d72..82a44c75382 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -84,6 +84,7 @@ module API expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :ci_default_git_depth + expose :ci_forward_deployment_enabled expose :public_builds, as: :public_jobs expose :build_git_strategy, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options| project.build_allow_git_fetch ? 'fetch' : 'clone' diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 8c20f5b8fc2..0364ba2ad9e 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -83,9 +83,18 @@ module API params :optional_filter_params_ee do end + params :optional_update_params_ce do + optional :ci_forward_deployment_enabled, type: Boolean, desc: 'Skip older deployment jobs that are still pending' + end + params :optional_update_params_ee do end + params :optional_update_params do + use :optional_update_params_ce + use :optional_update_params_ee + end + params :optional_container_expiration_policy_params do optional :cadence, type: String, desc: 'Container expiration policy cadence for recurring job' optional :keep_n, type: String, desc: 'Container expiration policy number of images to keep' @@ -108,6 +117,7 @@ module API :builds_access_level, :ci_config_path, :ci_default_git_depth, + :ci_forward_deployment_enabled, :container_registry_enabled, :container_expiration_policy_attributes, :default_branch, diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 5310f0c65f6..ecee76ae60c 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -353,7 +353,7 @@ module API optional :path, type: String, desc: 'The path of the repository' use :optional_project_params - use :optional_update_params_ee + use :optional_update_params at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index defe5420973..73f2116facc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15181,9 +15181,6 @@ msgstr "" msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. If a group label with the same title exists, it will also be merged. This action cannot be reversed." msgstr "" -msgid "Labels|and %{count} more" -msgstr "" - msgid "Language" msgstr "" diff --git a/rubocop/cop/graphql/id_type.rb b/rubocop/cop/graphql/id_type.rb index 9ec877900ec..96f90ac136a 100644 --- a/rubocop/cop/graphql/id_type.rb +++ b/rubocop/cop/graphql/id_type.rb @@ -6,7 +6,7 @@ module RuboCop class IDType < RuboCop::Cop::Cop MSG = 'Do not use GraphQL::ID_TYPE, use a specific GlobalIDType instead' - WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path].freeze + WHITELISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path].freeze def_node_search :graphql_id_type?, <<~PATTERN (send nil? :argument (_ #does_not_match?) (const (const nil? :GraphQL) :ID_TYPE) ...) diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index 9e26eca88f2..7a6e11d53d4 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -230,6 +230,21 @@ RSpec.describe Projects::Settings::CiCdController do end end + context 'when forward_deployment_enabled is not specified' do + let(:params) { { ci_cd_settings_attributes: { forward_deployment_enabled: false } } } + + before do + project.ci_cd_settings.update!(forward_deployment_enabled: nil) + end + + it 'sets forward deployment enabled' do + subject + + project.reload + expect(project.ci_forward_deployment_enabled).to eq(false) + end + end + context 'when max_artifacts_size is specified' do let(:params) { { max_artifacts_size: 10 } } diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index a47f2285e37..952a78ec79a 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -52,29 +52,20 @@ RSpec.describe 'Dashboard Merge Requests' do end context 'merge requests exist' do - let_it_be(:author_user) { create(:user) } let(:label) { create(:label) } let!(:assigned_merge_request) do create(:merge_request, assignees: [current_user], source_project: project, - author: author_user) - end - - let!(:review_requested_merge_request) do - create(:merge_request, - reviewers: [current_user], - source_branch: 'review', - source_project: project, - author: author_user) + author: create(:user)) end let!(:assigned_merge_request_from_fork) do create(:merge_request, source_branch: 'markdown', assignees: [current_user], target_project: public_project, source_project: forked_project, - author: author_user) + author: create(:user)) end let!(:authored_merge_request) do @@ -103,7 +94,7 @@ RSpec.describe 'Dashboard Merge Requests' do create(:merge_request, source_branch: 'fix', source_project: project, - author: author_user) + author: create(:user)) end before do @@ -120,10 +111,6 @@ RSpec.describe 'Dashboard Merge Requests' do expect(page).not_to have_content(labeled_merge_request.title) end - it 'shows review requested merge requests' do - expect(page).to have_content(review_requested_merge_request.title) - end - it 'shows authored merge requests', :js do reset_filters input_filtered_search("author:=#{current_user.to_reference}") diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 0358acc8dcc..ffc0ecc4966 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -64,7 +64,7 @@ RSpec.describe "Projects > Settings > Pipelines settings" do it 'updates forward_deployment_enabled' do visit project_settings_ci_cd_path(project) - checkbox = find_field('project_forward_deployment_enabled') + checkbox = find_field('project_ci_cd_settings_attributes_forward_deployment_enabled') expect(checkbox).to be_checked checkbox.set(false) @@ -79,7 +79,7 @@ RSpec.describe "Projects > Settings > Pipelines settings" do expect(page).to have_button('Save changes', disabled: false) end - checkbox = find_field('project_forward_deployment_enabled') + checkbox = find_field('project_ci_cd_settings_attributes_forward_deployment_enabled') expect(checkbox).not_to be_checked end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index b3d315e984e..68958e37001 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -333,8 +333,6 @@ RSpec.describe MergeRequestsFinder do end context 'assignee filtering' do - let_it_be(:user3) { create(:user) } - let(:issuables) { described_class.new(user, params).execute } it_behaves_like 'assignee ID filter' do @@ -353,6 +351,7 @@ RSpec.describe MergeRequestsFinder do merge_request3.assignees = [user2, user3] end + let_it_be(:user3) { create(:user) } let(:params) { { assignee_username: [user2.username, user3.username] } } let(:expected_issuables) { [merge_request3] } end @@ -367,6 +366,7 @@ RSpec.describe MergeRequestsFinder do end it_behaves_like 'no assignee filter' do + let_it_be(:user3) { create(:user) } let(:expected_issuables) { [merge_request4, merge_request5] } end @@ -374,54 +374,30 @@ RSpec.describe MergeRequestsFinder do let(:expected_issuables) { [merge_request1, merge_request2, merge_request3] } end - context 'with just reviewers' do - it_behaves_like 'assignee username filter' do - before do - merge_request4.reviewers = [user3] - merge_request4.assignees = [] - end - - let(:params) { { assignee_username: [user3.username] } } - let(:expected_issuables) { [merge_request4] } - end - end - - context 'with an additional reviewer' do - it_behaves_like 'assignee username filter' do - before do - merge_request3.assignees = [user3] - merge_request4.reviewers = [user3] - end + context 'filtering by group milestone' do + let(:group_milestone) { create(:milestone, group: group) } - let(:params) { { assignee_username: [user3.username] } } - let(:expected_issuables) { [merge_request3, merge_request4] } + before do + merge_request1.update!(milestone: group_milestone) + merge_request2.update!(milestone: group_milestone) end - end - end - context 'filtering by group milestone' do - let(:group_milestone) { create(:milestone, group: group) } + it 'returns merge requests assigned to that group milestone' do + params = { milestone_title: group_milestone.title } - before do - merge_request1.update!(milestone: group_milestone) - merge_request2.update!(milestone: group_milestone) - end - - it 'returns merge requests assigned to that group milestone' do - params = { milestone_title: group_milestone.title } - - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(merge_request1, merge_request2) - end + expect(merge_requests).to contain_exactly(merge_request1, merge_request2) + end - context 'using NOT' do - let(:params) { { not: { milestone_title: group_milestone.title } } } + context 'using NOT' do + let(:params) { { not: { milestone_title: group_milestone.title } } } - it 'returns MRs not assigned to that group milestone' do - merge_requests = described_class.new(user, params).execute + it 'returns MRs not assigned to that group milestone' do + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(merge_request3, merge_request4, merge_request5) + expect(merge_requests).to contain_exactly(merge_request3, merge_request4, merge_request5) + end end end end diff --git a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js index 49551568e6a..6a394251060 100644 --- a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js +++ b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js @@ -32,21 +32,16 @@ describe('Edit feature flag form', () => { } wrapper = shallowMount(EditFeatureFlag, { localVue, - propsData: { - environmentsEndpoint: 'environments.json', - projectId: '8', - featureFlagIssuesEndpoint: `${TEST_HOST}/feature_flags/5/issues`, + store, + provide: { showUserCallout: true, userCalloutId, userCalloutsPath, - }, - store, - provide: { glFeatures: { featureFlagsNewVersion: true, }, + ...opts, }, - ...opts, }); }; @@ -145,12 +140,6 @@ describe('Edit feature flag form', () => { }); }); - it('renders the related issues widget', () => { - const expected = `${TEST_HOST}/feature_flags/5/issues`; - - expect(wrapper.find(Form).props('featureFlagIssuesEndpoint')).toBe(expected); - }); - it('should track when the toggle is clicked', () => { const toggle = wrapper.find(GlToggle); const spy = mockTracking('_category_', toggle.element, jest.spyOn); @@ -164,7 +153,7 @@ describe('Edit feature flag form', () => { }); describe('without new version flags', () => { - beforeEach(() => factory({ provide: { glFeatures: { featureFlagsNewVersion: false } } })); + beforeEach(() => factory({ glFeatures: { featureFlagsNewVersion: false } })); it('should alert users that feature flags are changing soon', () => { expect(findAlert().text()).toBe(NEW_FLAG_ALERT); @@ -173,7 +162,7 @@ describe('Edit feature flag form', () => { describe('dismissing new version alert', () => { beforeEach(() => { - factory({ provide: { glFeatures: { featureFlagsNewVersion: false } } }); + factory({ glFeatures: { featureFlagsNewVersion: false } }); mock.onPost(userCalloutsPath, { feature_name: userCalloutId }).reply(200); findAlert().vm.$emit('dismiss'); return wrapper.vm.$nextTick(); diff --git a/spec/frontend/feature_flags/components/environments_dropdown_spec.js b/spec/frontend/feature_flags/components/environments_dropdown_spec.js index 2aa75ef6652..917f5f5ccd3 100644 --- a/spec/frontend/feature_flags/components/environments_dropdown_spec.js +++ b/spec/frontend/feature_flags/components/environments_dropdown_spec.js @@ -14,9 +14,11 @@ describe('Feature flags > Environments dropdown ', () => { const factory = props => { wrapper = shallowMount(EnvironmentsDropdown, { propsData: { - endpoint: `${TEST_HOST}/environments.json'`, ...props, }, + provide: { + environmentsEndpoint: `${TEST_HOST}/environments.json'`, + }, }); }; diff --git a/spec/frontend/feature_flags/components/form_spec.js b/spec/frontend/feature_flags/components/form_spec.js index 451bb4176ef..33c7eeb54b7 100644 --- a/spec/frontend/feature_flags/components/form_spec.js +++ b/spec/frontend/feature_flags/components/form_spec.js @@ -24,18 +24,23 @@ describe('feature flag form', () => { const requiredProps = { cancelPath: 'feature_flags', submitText: 'Create', + }; + + const requiredInjections = { environmentsEndpoint: '/environments.json', projectId: '1', + glFeatures: { + featureFlagPermissions: true, + featureFlagsNewVersion: true, + }, }; - const factory = (props = {}) => { + const factory = (props = {}, provide = {}) => { wrapper = shallowMount(Form, { - propsData: props, + propsData: { ...requiredProps, ...props }, provide: { - glFeatures: { - featureFlagPermissions: true, - featureFlagsNewVersion: true, - }, + ...requiredInjections, + ...provide, }, }); }; @@ -67,10 +72,13 @@ describe('feature flag form', () => { }); it('renders the related issues widget when the featureFlagIssuesEndpoint is provided', () => { - factory({ - ...requiredProps, - featureFlagIssuesEndpoint: '/some/endpoint', - }); + factory( + {}, + { + ...requiredInjections, + featureFlagIssuesEndpoint: '/some/endpoint', + }, + ); expect(wrapper.find(RelatedIssuesRoot).exists()).toBe(true); }); diff --git a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js index 10e9ed4d3bf..12dc98fbde8 100644 --- a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js +++ b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js @@ -14,7 +14,9 @@ describe('New Environments Dropdown', () => { beforeEach(() => { axiosMock = new MockAdapter(axios); - wrapper = shallowMount(NewEnvironmentsDropdown, { propsData: { endpoint: TEST_HOST } }); + wrapper = shallowMount(NewEnvironmentsDropdown, { + provide: { environmentsEndpoint: TEST_HOST }, + }); }); afterEach(() => { diff --git a/spec/frontend/feature_flags/components/new_feature_flag_spec.js b/spec/frontend/feature_flags/components/new_feature_flag_spec.js index 1844806ed95..dbc6e03d922 100644 --- a/spec/frontend/feature_flags/components/new_feature_flag_spec.js +++ b/spec/frontend/feature_flags/components/new_feature_flag_spec.js @@ -35,20 +35,18 @@ describe('New feature flag form', () => { } wrapper = shallowMount(NewFeatureFlag, { localVue, - propsData: { - environmentsEndpoint: 'environments.json', - projectId: '8', + store, + provide: { showUserCallout: true, userCalloutId, userCalloutsPath, - }, - store, - provide: { + environmentsEndpoint: 'environments.json', + projectId: '8', glFeatures: { featureFlagsNewVersion: true, }, + ...opts, }, - ...opts, }); }; @@ -80,10 +78,6 @@ describe('New feature flag form', () => { expect(wrapper.find(Form).exists()).toEqual(true); }); - it('does not render the related issues widget', () => { - expect(wrapper.find(Form).props('featureFlagIssuesEndpoint')).toBe(''); - }); - it('should render default * row', () => { const defaultScope = { id: expect.any(String), @@ -102,10 +96,6 @@ describe('New feature flag form', () => { expect(wrapper.find(GlAlert).exists()).toBe(false); }); - it('should pass in the project ID', () => { - expect(wrapper.find(Form).props('projectId')).toBe('8'); - }); - it('has an all users strategy by default', () => { const strategies = wrapper.find(Form).props('strategies'); @@ -113,7 +103,7 @@ describe('New feature flag form', () => { }); describe('without new version flags', () => { - beforeEach(() => factory({ provide: { glFeatures: { featureFlagsNewVersion: false } } })); + beforeEach(() => factory({ glFeatures: { featureFlagsNewVersion: false } })); it('should alert users that feature flags are changing soon', () => { expect(findAlert().text()).toBe(NEW_FLAG_ALERT); @@ -126,7 +116,7 @@ describe('New feature flag form', () => { beforeEach(() => { mock = new MockAdapter(axios); mock.onPost(userCalloutsPath, { feature_name: userCalloutId }).reply(200); - factory({ provide: { glFeatures: { featureFlagsNewVersion: false } } }); + factory({ glFeatures: { featureFlagsNewVersion: false } }); findAlert().vm.$emit('dismiss'); return wrapper.vm.$nextTick(); }); diff --git a/spec/frontend/feature_flags/components/strategy_spec.js b/spec/frontend/feature_flags/components/strategy_spec.js index 1e3e1a76afb..7d6700ba184 100644 --- a/spec/frontend/feature_flags/components/strategy_spec.js +++ b/spec/frontend/feature_flags/components/strategy_spec.js @@ -18,6 +18,7 @@ import { userList } from '../mock_data'; const provide = { strategyTypeDocsPagePath: 'link-to-strategy-docs', environmentsScopeDocsPath: 'link-scope-docs', + environmentsEndpoint: '', }; describe('Feature flags strategy', () => { @@ -31,7 +32,6 @@ describe('Feature flags strategy', () => { propsData: { strategy: {}, index: 0, - endpoint: '', userLists: [userList], }, provide, @@ -52,7 +52,7 @@ describe('Feature flags strategy', () => { }); describe('helper links', () => { - const propsData = { strategy: {}, index: 0, endpoint: '', userLists: [userList] }; + const propsData = { strategy: {}, index: 0, userLists: [userList] }; factory({ propsData, provide }); it('should display 2 helper links', () => { @@ -76,7 +76,7 @@ describe('Feature flags strategy', () => { beforeEach(() => { strategy = { name, parameters: {}, scopes: [] }; - propsData = { strategy, index: 0, endpoint: '' }; + propsData = { strategy, index: 0 }; factory({ propsData, provide }); return wrapper.vm.$nextTick(); }); @@ -102,7 +102,7 @@ describe('Feature flags strategy', () => { parameters: { percentage: '50', groupId: 'default' }, scopes: [{ environmentScope: 'production' }], }; - const propsData = { strategy, index: 0, endpoint: '' }; + const propsData = { strategy, index: 0 }; factory({ propsData, provide }); }); @@ -123,7 +123,7 @@ describe('Feature flags strategy', () => { parameters: { percentage: '50', groupId: 'default' }, scopes: [{ environmentScope: 'production' }], }; - const propsData = { strategy, index: 0, endpoint: '' }; + const propsData = { strategy, index: 0 }; factory({ propsData, provide }); }); @@ -152,7 +152,7 @@ describe('Feature flags strategy', () => { parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID }, scopes: [{ environmentScope: '*' }], }; - const propsData = { strategy, index: 0, endpoint: '' }; + const propsData = { strategy, index: 0 }; factory({ propsData, provide }); }); @@ -221,7 +221,7 @@ describe('Feature flags strategy', () => { parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID }, scopes: [], }; - const propsData = { strategy, index: 0, endpoint: '' }; + const propsData = { strategy, index: 0 }; factory({ propsData, provide }); }); diff --git a/spec/frontend/fixtures/static/issue_sidebar_label.html b/spec/frontend/fixtures/static/issue_sidebar_label.html deleted file mode 100644 index ec8fb30f219..00000000000 --- a/spec/frontend/fixtures/static/issue_sidebar_label.html +++ /dev/null @@ -1,26 +0,0 @@ -<div class="block labels"> -<div class="sidebar-collapsed-icon js-sidebar-labels-tooltip"></div> -<div class="title hide-collapsed"> -<a class="edit-link float-right" href="#"> -Edit -</a> -</div> -<div class="selectbox hide-collapsed" style="display: none;"> -<div class="dropdown"> -<button class="dropdown-menu-toggle js-label-select js-multiselect" data-ability-name="issue" data-field-name="issue[label_names][]" data-issue-update="/root/test/issues/2.json" data-labels="/root/test/labels.json" data-project-id="12" data-show-any="true" data-show-no="true" data-toggle="dropdown" type="button"> -<span class="dropdown-toggle-text"> -Label -</span> -<i class="fa fa-chevron-down"></i> -</button> -<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable"> -<div class="dropdown-page-one"> -<div class="dropdown-content"></div> -<div class="dropdown-loading"> -<i class="fa fa-spinner fa-spin"></i> -</div> -</div> -</div> -</div> -</div> -</div> diff --git a/spec/frontend/labels_issue_sidebar_spec.js b/spec/frontend/labels_issue_sidebar_spec.js deleted file mode 100644 index f74547c0554..00000000000 --- a/spec/frontend/labels_issue_sidebar_spec.js +++ /dev/null @@ -1,98 +0,0 @@ -/* eslint-disable no-new */ - -import $ from 'jquery'; -import MockAdapter from 'axios-mock-adapter'; -import { shuffle } from 'lodash'; -import axios from '~/lib/utils/axios_utils'; -import IssuableContext from '~/issuable_context'; -import LabelsSelect from '~/labels_select'; - -import 'select2'; -import '~/api'; -import '~/create_label'; -import '~/users_select'; - -let saveLabelCount = 0; -let mock; - -function testLabelClicks(labelOrder, done) { - $('.edit-link') - .get(0) - .click(); - - jest.runOnlyPendingTimers(); - - setImmediate(() => { - const labelsInDropdown = $('.dropdown-content a'); - - expect(labelsInDropdown.length).toBe(10); - - const arrayOfLabels = labelsInDropdown.get(); - const randomArrayOfLabels = shuffle(arrayOfLabels); - randomArrayOfLabels.forEach((label, i) => { - if (i < saveLabelCount) { - $(label).click(); - } - }); - - $('.edit-link') - .get(0) - .click(); - - setImmediate(() => { - expect($('.sidebar-collapsed-icon').attr('data-original-title')).toBe(labelOrder); - done(); - }); - }); -} - -describe('Issue dropdown sidebar', () => { - preloadFixtures('static/issue_sidebar_label.html'); - - beforeEach(() => { - loadFixtures('static/issue_sidebar_label.html'); - - mock = new MockAdapter(axios); - - new IssuableContext('{"id":1,"name":"Administrator","username":"root"}'); - new LabelsSelect(); - - mock.onGet('/root/test/labels.json').reply(() => { - const labels = Array(10) - .fill() - .map((_val, i) => ({ - id: i, - title: `test ${i}`, - color: '#5CB85C', - })); - - return [200, labels]; - }); - - mock.onPut('/root/test/issues/2.json').reply(() => { - const labels = Array(saveLabelCount) - .fill() - .map((_val, i) => ({ - id: i, - title: `test ${i}`, - color: '#5CB85C', - })); - - return [200, { labels }]; - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('changes collapsed tooltip when changing labels when less than 5', done => { - saveLabelCount = 5; - testLabelClicks('test 0, test 1, test 2, test 3, test 4', done); - }); - - it('changes collapsed tooltip when changing labels when more than 5', done => { - saveLabelCount = 6; - testLabelClicks('test 0, test 1, test 2, test 3, test 4, and 1 more', done); - }); -}); diff --git a/spec/graphql/mutations/issues/move_spec.rb b/spec/graphql/mutations/issues/move_spec.rb new file mode 100644 index 00000000000..c8e9c556a3f --- /dev/null +++ b/spec/graphql/mutations/issues/move_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Issues::Move do + let_it_be(:issue) { create(:issue) } + let_it_be(:user) { create(:user) } + let_it_be(:target_project) { create(:project) } + + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + + describe '#resolve' do + subject(:resolve) { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, target_project_path: target_project.full_path) } + + it 'raises an error if the resource is not accessible to the user' do + expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + + context 'when user does not have permissions' do + before do + issue.project.add_developer(user) + end + + it 'returns error message' do + expect(resolve[:issue]).to eq(nil) + expect(resolve[:errors].first).to eq('Cannot move issue due to insufficient permissions!') + end + end + + context 'when user has sufficient permissions' do + before do + issue.project.add_developer(user) + target_project.add_developer(user) + end + + it 'moves issue' do + expect(resolve[:issue].project).to eq(target_project) + end + end + end +end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 2475620221c..e8e5adaa274 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -44,23 +44,6 @@ RSpec.describe IssuablesHelper do end end - describe '#issuable_labels_tooltip' do - let(:label_entity) { LabelEntity.represent(label).as_json } - let(:label2_entity) { LabelEntity.represent(label2).as_json } - - it 'returns label text with no labels' do - expect(issuable_labels_tooltip([])).to eq(_('Labels')) - end - - it 'returns label text with labels within max limit' do - expect(issuable_labels_tooltip([label_entity])).to eq(label[:title]) - end - - it 'returns label text with labels exceeding max limit' do - expect(issuable_labels_tooltip([label_entity, label2_entity], limit: 1)).to eq("#{label[:title]}, and 1 more") - end - end - describe '#issuables_state_counter_text' do let(:user) { create(:user) } diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 555decd284a..b93dc03e434 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -244,26 +244,6 @@ RSpec.describe LabelsHelper do end end - describe 'label_from_hash' do - it 'builds a group label with whitelisted attributes' do - label = label_from_hash({ title: 'foo', color: 'bar', id: 1, group_id: 1 }) - - expect(label).to be_a(GroupLabel) - expect(label.id).to be_nil - expect(label.title).to eq('foo') - expect(label.color).to eq('bar') - end - - it 'builds a project label with whitelisted attributes' do - label = label_from_hash({ title: 'foo', color: 'bar', id: 1, project_id: 1 }) - - expect(label).to be_a(ProjectLabel) - expect(label.id).to be_nil - expect(label.title).to eq('foo') - expect(label.color).to eq('bar') - end - end - describe '#label_status_tooltip' do let(:status) { 'unsubscribed'.inquiry } diff --git a/spec/requests/api/graphql/mutations/issues/move_spec.rb b/spec/requests/api/graphql/mutations/issues/move_spec.rb new file mode 100644 index 00000000000..5bbaff61edd --- /dev/null +++ b/spec/requests/api/graphql/mutations/issues/move_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Moving an issue' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:issue) { create(:issue) } + let_it_be(:target_project) { create(:project) } + + let(:mutation) do + variables = { + project_path: issue.project.full_path, + target_project_path: target_project.full_path, + iid: issue.iid.to_s + } + + graphql_mutation(:issue_move, variables, + <<-QL.strip_heredoc + clientMutationId + errors + issue { + title + } + QL + ) + end + + def mutation_response + graphql_mutation_response(:issue_move) + end + + context 'when the user is not allowed to read source project' do + it 'returns an error' do + error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action" + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to include(a_hash_including('message' => error)) + end + end + + context 'when the user is not allowed to move issue to target project' do + before do + issue.project.add_developer(user) + end + + it 'returns an error' do + error = "Cannot move issue due to insufficient permissions!" + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors'][0]).to eq(error) + end + end + + context 'when the user is allowed to move issue' do + before do + issue.project.add_developer(user) + target_project.add_developer(user) + end + + it 'moves the issue' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response.dig('issue', 'title')).to eq(issue.title) + expect(issue.reload.state).to eq('closed') + expect(target_project.issues.find_by_title(issue.title)).to be_present + end + end +end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 831b0d6e678..2abcb39a1c8 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1615,6 +1615,7 @@ RSpec.describe API::Projects do expect(json_response['allow_merge_on_skipped_pipeline']).to eq(project.allow_merge_on_skipped_pipeline) expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) expect(json_response['ci_default_git_depth']).to eq(project.ci_default_git_depth) + expect(json_response['ci_forward_deployment_enabled']).to eq(project.ci_forward_deployment_enabled) expect(json_response['merge_method']).to eq(project.merge_method.to_s) expect(json_response['readme_url']).to eq(project.readme_url) expect(json_response).to have_key 'packages_enabled' @@ -2607,6 +2608,7 @@ RSpec.describe API::Projects do merge_requests_enabled: true, merge_method: 'ff', ci_default_git_depth: 20, + ci_forward_deployment_enabled: false, description: 'new description' } put api("/projects/#{project3.id}", user4), params: project_param |