Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-14 18:10:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-14 18:10:46 +0300
commit8106ac487c3b52471e2ca894c65c13162c2fb1a8 (patch)
tree12a07c7dd794babe870477f3cd42f15392a82355
parent18873553de98259d0558157f78198b38ddd02b31 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.eslintrc.yml44
-rw-r--r--CHANGELOG.md21
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/api.js40
-rw-r--r--app/assets/javascripts/api/api_utils.js5
-rw-r--r--app/assets/javascripts/api/constants.js1
-rw-r--r--app/assets/javascripts/api/groups_api.js22
-rw-r--r--app/assets/javascripts/api/projects_api.js27
-rw-r--r--app/assets/javascripts/api/user_api.js66
-rw-r--r--app/assets/javascripts/boards/components/project_select_deprecated.vue2
-rw-r--r--app/assets/javascripts/commons/nav/user_merge_requests.js4
-rw-r--r--app/assets/javascripts/frequent_items/store/actions.js8
-rw-r--r--app/assets/javascripts/invite_members/components/members_token_select.vue4
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js8
-rw-r--r--app/assets/javascripts/notes/stores/actions.js9
-rw-r--r--app/assets/javascripts/performance_bar/index.js23
-rw-r--r--app/assets/javascripts/rest_api.js15
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue10
-rw-r--r--app/controllers/admin/projects_controller.rb4
-rw-r--r--app/controllers/oauth/authorizations_controller.rb23
-rw-r--r--app/helpers/page_layout_helper.rb2
-rw-r--r--app/models/ci/group.rb15
-rw-r--r--app/models/concerns/boards/listable.rb8
-rw-r--r--app/models/project_services/jira_service.rb10
-rw-r--r--app/services/ci/destroy_expired_job_artifacts_service.rb68
-rw-r--r--app/services/draft_notes/base_service.rb4
-rw-r--r--app/services/draft_notes/create_service.rb4
-rw-r--r--app/services/draft_notes/publish_service.rb1
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/shared/empty_states/_snippets.html.haml4
-rw-r--r--app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb2
-rw-r--r--app/workers/expire_build_artifacts_worker.rb4
-rw-r--r--changelogs/unreleased/243857-matrix-job-status-allow-failure.yml5
-rw-r--r--changelogs/unreleased/270059-feature-flag-rollout-of-ci_pipeline_editor_page.yml5
-rw-r--r--changelogs/unreleased/284640-fix-newline-in-custom-server-hook-renders-improperly.yml5
-rw-r--r--changelogs/unreleased/292822-track-comment-related-metrics-for-a-merge-request-3.yml5
-rw-r--r--changelogs/unreleased/30477-vfazio-alert-transfer-error.yml5
-rw-r--r--changelogs/unreleased/ci-pipeline-artifacts-removal-cleanup.yml6
-rw-r--r--changelogs/unreleased/default-enable-set-user-availability-status.yml5
-rw-r--r--changelogs/unreleased/doc-218997-fj-add-doc-for-snippet-api-repository-moves.yml5
-rw-r--r--changelogs/unreleased/ph-285635-suggestionAppliedResolvedStatus.yml5
-rw-r--r--changelogs/unreleased/rename-backlog-list-to-open-in-backend.yml5
-rw-r--r--changelogs/unreleased/terraform-0-14-cache.yml5
-rw-r--r--changelogs/unreleased/yo-master-patch-32789.yml5
-rw-r--r--changelogs/unreleased/yo-master-patch-66129.yml5
-rw-r--r--config/feature_flags/development/ci_pipeline_editor_page.yml2
-rw-r--r--config/feature_flags/development/ci_slow_artifacts_removal.yml8
-rw-r--r--config/feature_flags/development/ci_split_pipeline_artifacts_removal.yml8
-rw-r--r--config/feature_flags/development/set_user_availability_status.yml2
-rw-r--r--config/feature_flags/development/usage_data_i_code_review_user_create_review_note.yml8
-rw-r--r--config/feature_flags/development/usage_data_i_code_review_user_publish_review.yml8
-rw-r--r--doc/administration/gitaly/praefect.md4
-rw-r--r--doc/administration/operations/moving_repositories.md6
-rw-r--r--doc/api/api_resources.md1
-rw-r--r--doc/api/project_repository_storage_moves.md3
-rw-r--r--doc/api/snippet_repository_storage_moves.md293
-rw-r--r--doc/ci/pipeline_editor/index.md16
-rw-r--r--doc/development/code_review.md1
-rw-r--r--doc/development/fe_guide/editor_lite.md9
-rw-r--r--doc/user/profile/index.md21
-rw-r--r--doc/user/project/quick_actions.md1
-rw-r--r--lib/gitlab/ci/features.rb2
-rw-r--r--lib/gitlab/ci/status/group/factory.rb4
-rw-r--r--lib/gitlab/ci/templates/Terraform.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Terraform/Base.latest.gitlab-ci.yml1
-rw-r--r--lib/gitlab/git/repository.rb4
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb5
-rw-r--r--lib/gitlab/usage_data_counters/known_events/common.yml10
-rw-r--r--lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb10
-rw-r--r--locale/gitlab.pot15
-rw-r--r--spec/controllers/admin/projects_controller_spec.rb30
-rw-r--r--spec/controllers/oauth/authorizations_controller_spec.rb33
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/frontend/api/api_utils_spec.js35
-rw-r--r--spec/frontend/commons/nav/user_merge_requests_spec.js18
-rw-r--r--spec/frontend/invite_members/components/members_token_select_spec.js11
-rw-r--r--spec/frontend/lib/utils/users_cache_spec.js10
-rw-r--r--spec/frontend/notes/stores/actions_spec.js5
-rw-r--r--spec/frontend/performance_bar/index_spec.js11
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js17
-rw-r--r--spec/lib/gitlab/ci/status/group/factory_spec.rb5
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb5
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb5
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb16
-rw-r--r--spec/models/ci/group_spec.rb12
-rw-r--r--spec/models/project_services/jira_service_spec.rb53
-rw-r--r--spec/services/ci/destroy_expired_job_artifacts_service_spec.rb78
-rw-r--r--spec/services/draft_notes/create_service_spec.rb17
-rw-r--r--spec/services/draft_notes/publish_service_spec.rb22
-rw-r--r--spec/support/shared_examples/models/boards/listable_shared_examples.rb6
-rw-r--r--spec/workers/expire_build_artifacts_worker_spec.rb4
95 files changed, 1027 insertions, 351 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index c44fe588187..a764f749785 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -50,50 +50,6 @@ rules:
# various vue lint rules as they were in eslint-plugin-vue@6, or disabling
# new ones, to ease migration to v7, so violations of each can be fixed
# separately.
- vue/order-in-components:
- - error
- # This is the order from eslint-plugin-vue@6.2.2
- - order:
- - el
- - name
- - parent
- - functional
- -
- - delimiters
- - comments
- -
- - components
- - directives
- - filters
- - extends
- - mixins
- - inheritAttrs
- - model
- -
- - props
- - propsData
- - fetch
- - asyncData
- - data
- - computed
- - watch
- -
- - beforeCreate
- - created
- - beforeMount
- - mounted
- - beforeUpdate
- - updated
- - activated
- - deactivated
- - beforeDestroy
- - destroyed
- - methods
- - head
- -
- - template
- - render
- - renderError
vue/no-mutating-props: off
vue/one-component-per-file: off
vue/no-lone-template: off
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56ddb1350bb..3b6780c7797 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 13.7.4 (2021-01-13)
+
+### Security (1 change)
+
+- Deny implicit flow for confidential apps.
+
+
## 13.7.3 (2021-01-08)
### Fixed (7 changes)
@@ -497,6 +504,13 @@ entry.
- Update GitLab Workhorse to v8.57.0.
+## 13.6.5 (2021-01-13)
+
+### Security (1 change)
+
+- Deny implicit flow for confidential apps.
+
+
## 13.6.4 (2021-01-07)
### Security (7 changes)
@@ -1068,6 +1082,13 @@ entry.
- Change wording on the project remove fork page. !47878
+## 13.5.7 (2021-01-13)
+
+### Security (1 change)
+
+- Deny implicit flow for confidential apps.
+
+
## 13.5.6 (2021-01-07)
### Security (7 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index b8544af8ff8..cf7e7cefb17 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-a6674b359a02a4bf0549dcaa77ac05b1f4850831
+3083074640633df94cbeee611795a6fc6d8c5607
diff --git a/Gemfile b/Gemfile
index b802e93edf1..3b76862fc21 100644
--- a/Gemfile
+++ b/Gemfile
@@ -465,7 +465,7 @@ group :ed25519 do
end
# Gitaly GRPC protocol definitions
-gem 'gitaly', '~> 13.7.0.pre.rc1'
+gem 'gitaly', '~> 13.8.0.pre.rc2'
gem 'grpc', '~> 1.30.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index ffe87bcd1df..e91ea69279b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -417,7 +417,7 @@ GEM
rails (>= 3.2.0)
git (1.7.0)
rchardet (~> 1.8)
- gitaly (13.7.0.pre.rc1)
+ gitaly (13.8.0.pre.rc2)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-chronic (0.10.5)
@@ -1358,7 +1358,7 @@ DEPENDENCIES
gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly (~> 13.7.0.pre.rc1)
+ gitaly (~> 13.8.0.pre.rc2)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-experiment (~> 0.4.5)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 2f3a56ec046..e0b9643f509 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -5,6 +5,12 @@ import { __ } from '~/locale';
const DEFAULT_PER_PAGE = 20;
+/**
+ * Slow deprecation Notice: Please rather use for new calls
+ * or during refactors /rest_api as this is doing named exports
+ * which support treeshaking
+ */
+
const Api = {
DEFAULT_PER_PAGE,
groupsPath: '/api/:version/groups.json',
@@ -152,7 +158,10 @@ const Api = {
});
},
- // Return groups list. Filtered by query
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `getGroups` method in `~/rest_api` instead.
+ */
groups(query, options, callback = () => {}) {
const url = Api.buildUrl(Api.groupsPath);
return axios
@@ -188,7 +197,10 @@ const Api = {
.then(({ data }) => callback(data));
},
- // Return projects list. Filtered by query
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `getProjects` method in `~/rest_api` instead.
+ */
projects(query, options, callback = () => {}) {
const url = Api.buildUrl(Api.projectsPath);
const defaults = {
@@ -521,6 +533,10 @@ const Api = {
.replace(':namespace_path', namespacePath);
},
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `getUsers` method in `~/rest_api` instead.
+ */
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return axios.get(url, {
@@ -532,6 +548,10 @@ const Api = {
});
},
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `getUser` method in `~/rest_api` instead.
+ */
user(id, options) {
const url = Api.buildUrl(this.userPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {
@@ -539,11 +559,19 @@ const Api = {
});
},
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `getUserCounts` method in `~/rest_api` instead.
+ */
userCounts() {
const url = Api.buildUrl(this.userCountsPath);
return axios.get(url);
},
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `getUserStatus` method in `~/rest_api` instead.
+ */
userStatus(id, options) {
const url = Api.buildUrl(this.userStatusPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {
@@ -551,6 +579,10 @@ const Api = {
});
},
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `getUserProjects` method in `~/rest_api` instead.
+ */
userProjects(userId, query, options, callback) {
const url = Api.buildUrl(Api.userProjectsPath).replace(':id', userId);
const defaults = {
@@ -586,6 +618,10 @@ const Api = {
});
},
+ /**
+ * @deprecated This method will be removed soon. Use the
+ * `updateUserStatus` method in `~/rest_api` instead.
+ */
postUserStatus({ emoji, message, availability }) {
const url = Api.buildUrl(this.userPostStatusPath);
diff --git a/app/assets/javascripts/api/api_utils.js b/app/assets/javascripts/api/api_utils.js
new file mode 100644
index 00000000000..42eb5e7aaf3
--- /dev/null
+++ b/app/assets/javascripts/api/api_utils.js
@@ -0,0 +1,5 @@
+import { joinPaths } from '../lib/utils/url_utility';
+
+export function buildApiUrl(url) {
+ return joinPaths('/', gon.relative_url_root || '', url.replace(':version', gon.api_version));
+}
diff --git a/app/assets/javascripts/api/constants.js b/app/assets/javascripts/api/constants.js
new file mode 100644
index 00000000000..b6c720a85f3
--- /dev/null
+++ b/app/assets/javascripts/api/constants.js
@@ -0,0 +1 @@
+export const DEFAULT_PER_PAGE = 20;
diff --git a/app/assets/javascripts/api/groups_api.js b/app/assets/javascripts/api/groups_api.js
new file mode 100644
index 00000000000..d4ba46656e6
--- /dev/null
+++ b/app/assets/javascripts/api/groups_api.js
@@ -0,0 +1,22 @@
+import axios from '../lib/utils/axios_utils';
+import { buildApiUrl } from './api_utils';
+import { DEFAULT_PER_PAGE } from './constants';
+
+const GROUPS_PATH = '/api/:version/groups.json';
+
+export function getGroups(query, options, callback = () => {}) {
+ const url = buildApiUrl(GROUPS_PATH);
+ return axios
+ .get(url, {
+ params: {
+ search: query,
+ per_page: DEFAULT_PER_PAGE,
+ ...options,
+ },
+ })
+ .then(({ data }) => {
+ callback(data);
+
+ return data;
+ });
+}
diff --git a/app/assets/javascripts/api/projects_api.js b/app/assets/javascripts/api/projects_api.js
new file mode 100644
index 00000000000..d9a2467cff3
--- /dev/null
+++ b/app/assets/javascripts/api/projects_api.js
@@ -0,0 +1,27 @@
+import axios from '../lib/utils/axios_utils';
+import { buildApiUrl } from './api_utils';
+import { DEFAULT_PER_PAGE } from './constants';
+
+const PROJECTS_PATH = '/api/:version/projects.json';
+
+export function getProjects(query, options, callback = () => {}) {
+ const url = buildApiUrl(PROJECTS_PATH);
+ const defaults = {
+ search: query,
+ per_page: DEFAULT_PER_PAGE,
+ simple: true,
+ };
+
+ if (gon.current_user_id) {
+ defaults.membership = true;
+ }
+
+ return axios
+ .get(url, {
+ params: Object.assign(defaults, options),
+ })
+ .then(({ data, headers }) => {
+ callback(data);
+ return { data, headers };
+ });
+}
diff --git a/app/assets/javascripts/api/user_api.js b/app/assets/javascripts/api/user_api.js
new file mode 100644
index 00000000000..e5983ec3c58
--- /dev/null
+++ b/app/assets/javascripts/api/user_api.js
@@ -0,0 +1,66 @@
+import axios from '../lib/utils/axios_utils';
+import { buildApiUrl } from './api_utils';
+import { DEFAULT_PER_PAGE } from './constants';
+import { deprecatedCreateFlash as flash } from '~/flash';
+import { __ } from '~/locale';
+
+const USER_COUNTS_PATH = '/api/:version/user_counts';
+const USERS_PATH = '/api/:version/users.json';
+const USER_PATH = '/api/:version/users/:id';
+const USER_STATUS_PATH = '/api/:version/users/:id/status';
+const USER_PROJECTS_PATH = '/api/:version/users/:id/projects';
+const USER_POST_STATUS_PATH = '/api/:version/user/status';
+
+export function getUsers(query, options) {
+ const url = buildApiUrl(USERS_PATH);
+ return axios.get(url, {
+ params: {
+ search: query,
+ per_page: DEFAULT_PER_PAGE,
+ ...options,
+ },
+ });
+}
+
+export function getUser(id, options) {
+ const url = buildApiUrl(USER_PATH).replace(':id', encodeURIComponent(id));
+ return axios.get(url, {
+ params: options,
+ });
+}
+
+export function getUserCounts() {
+ const url = buildApiUrl(USER_COUNTS_PATH);
+ return axios.get(url);
+}
+
+export function getUserStatus(id, options) {
+ const url = buildApiUrl(USER_STATUS_PATH).replace(':id', encodeURIComponent(id));
+ return axios.get(url, {
+ params: options,
+ });
+}
+
+export function getUserProjects(userId, query, options, callback) {
+ const url = buildApiUrl(USER_PROJECTS_PATH).replace(':id', userId);
+ const defaults = {
+ search: query,
+ per_page: DEFAULT_PER_PAGE,
+ };
+ return axios
+ .get(url, {
+ params: { ...defaults, ...options },
+ })
+ .then(({ data }) => callback(data))
+ .catch(() => flash(__('Something went wrong while fetching projects')));
+}
+
+export function updateUserStatus({ emoji, message, availability }) {
+ const url = buildApiUrl(USER_POST_STATUS_PATH);
+
+ return axios.put(url, {
+ emoji,
+ message,
+ availability,
+ });
+}
diff --git a/app/assets/javascripts/boards/components/project_select_deprecated.vue b/app/assets/javascripts/boards/components/project_select_deprecated.vue
index f14db42d43c..a043dc575ca 100644
--- a/app/assets/javascripts/boards/components/project_select_deprecated.vue
+++ b/app/assets/javascripts/boards/components/project_select_deprecated.vue
@@ -33,13 +33,13 @@ export default {
GlDropdownText,
GlSearchBoxByType,
},
+ inject: ['groupId'],
props: {
list: {
type: Object,
required: true,
},
},
- inject: ['groupId'],
data() {
return {
initialLoading: true,
diff --git a/app/assets/javascripts/commons/nav/user_merge_requests.js b/app/assets/javascripts/commons/nav/user_merge_requests.js
index eab2b5a31d7..84ab728274f 100644
--- a/app/assets/javascripts/commons/nav/user_merge_requests.js
+++ b/app/assets/javascripts/commons/nav/user_merge_requests.js
@@ -1,4 +1,4 @@
-import Api from '~/api';
+import { getUserCounts } from '~/rest_api';
let channel;
@@ -30,7 +30,7 @@ function updateMergeRequestCounts(newCount) {
* Refresh user counts (and broadcast if open)
*/
export function refreshUserMergeRequestCounts() {
- return Api.userCounts()
+ return getUserCounts()
.then(({ data }) => {
const assignedMergeRequests = data.assigned_merge_requests;
const reviewerMergeRequests = data.review_requested_merge_requests;
diff --git a/app/assets/javascripts/frequent_items/store/actions.js b/app/assets/javascripts/frequent_items/store/actions.js
index b16501aeb09..f4156487625 100644
--- a/app/assets/javascripts/frequent_items/store/actions.js
+++ b/app/assets/javascripts/frequent_items/store/actions.js
@@ -1,7 +1,7 @@
-import Api from '~/api';
import AccessorUtilities from '~/lib/utils/accessor';
import * as types from './mutation_types';
import { getTopFrequentItems } from '../utils';
+import { getGroups, getProjects } from '~/rest_api';
export const setNamespace = ({ commit }, namespace) => {
commit(types.SET_NAMESPACE, namespace);
@@ -54,11 +54,15 @@ export const fetchSearchedItems = ({ state, dispatch }, searchQuery) => {
membership: Boolean(gon.current_user_id),
};
+ let searchFunction;
if (state.namespace === 'projects') {
+ searchFunction = getProjects;
params.order_by = 'last_activity_at';
+ } else {
+ searchFunction = getGroups;
}
- return Api[state.namespace](searchQuery, params)
+ return searchFunction(searchQuery, params)
.then((results) => {
dispatch('receiveSearchedItemsSuccess', results);
})
diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue
index b54812dfd96..627d4ab2771 100644
--- a/app/assets/javascripts/invite_members/components/members_token_select.vue
+++ b/app/assets/javascripts/invite_members/components/members_token_select.vue
@@ -3,7 +3,7 @@ import { debounce } from 'lodash';
import { GlTokenSelector, GlAvatar, GlAvatarLabeled, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import { USER_SEARCH_DELAY } from '../constants';
-import Api from '~/api';
+import { getUsers } from '~/rest_api';
export default {
components: {
@@ -54,7 +54,7 @@ export default {
this.retrieveUsers(query);
},
retrieveUsers: debounce(function debouncedRetrieveUsers() {
- return Api.users(this.query, this.$options.queryOptions)
+ return getUsers(this.query, this.$options.queryOptions)
.then((response) => {
this.users = response.data.map((token) => ({
id: token.id,
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index 9f980fd4899..54f69ef8e1b 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -1,4 +1,4 @@
-import Api from '../../api';
+import { getUsers, getUser, getUserStatus } from '~/rest_api';
import Cache from './cache';
class UsersCache extends Cache {
@@ -7,7 +7,7 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(username));
}
- return Api.users('', { username }).then(({ data }) => {
+ return getUsers('', { username }).then(({ data }) => {
if (!data.length) {
throw new Error(`User "${username}" could not be found!`);
}
@@ -28,7 +28,7 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(userId));
}
- return Api.user(userId).then(({ data }) => {
+ return getUser(userId).then(({ data }) => {
this.internalStorage[userId] = data;
return data;
});
@@ -40,7 +40,7 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(userId).status);
}
- return Api.userStatus(userId).then(({ data }) => {
+ return getUserStatus(userId).then(({ data }) => {
if (!this.hasData(userId)) {
this.internalStorage[userId] = {};
}
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 2480ab82304..0ab781c13d3 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -559,7 +559,7 @@ export const updateResolvableDiscussionsCounts = ({ commit }) =>
export const submitSuggestion = (
{ commit, dispatch },
- { discussionId, noteId, suggestionId, flashContainer },
+ { discussionId, suggestionId, flashContainer },
) => {
const dispatchResolveDiscussion = () =>
dispatch('resolveDiscussion', { discussionId }).catch(() => {});
@@ -568,7 +568,6 @@ export const submitSuggestion = (
dispatch('stopPolling');
return Api.applySuggestion(suggestionId)
- .then(() => commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }))
.then(dispatchResolveDiscussion)
.catch((err) => {
const defaultMessage = __(
@@ -590,11 +589,6 @@ export const submitSuggestion = (
export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContainer }) => {
const suggestionIds = state.batchSuggestionsInfo.map(({ suggestionId }) => suggestionId);
- const applyAllSuggestions = () =>
- state.batchSuggestionsInfo.map((suggestionInfo) =>
- commit(types.APPLY_SUGGESTION, suggestionInfo),
- );
-
const resolveAllDiscussions = () =>
state.batchSuggestionsInfo.map((suggestionInfo) => {
const { discussionId } = suggestionInfo;
@@ -606,7 +600,6 @@ export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContai
dispatch('stopPolling');
return Api.applySuggestionBatch(suggestionIds)
- .then(() => Promise.all(applyAllSuggestions()))
.then(() => Promise.all(resolveAllDiscussions()))
.then(() => commit(types.CLEAR_SUGGESTION_BATCH))
.catch((err) => {
diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js
index 8ecf5971392..0d5c294ea56 100644
--- a/app/assets/javascripts/performance_bar/index.js
+++ b/app/assets/javascripts/performance_bar/index.js
@@ -11,6 +11,9 @@ import initPerformanceBarLog from './performance_bar_log';
Vue.use(Translate);
const initPerformanceBar = (el) => {
+ if (!el) {
+ return undefined;
+ }
const performanceBarData = el.dataset;
return new Vue({
@@ -126,25 +129,7 @@ const initPerformanceBar = (el) => {
});
};
-let loadedPeekBar = false;
-function loadBar() {
- const jsPeek = document.querySelector('#js-peek');
- if (!loadedPeekBar && jsPeek) {
- loadedPeekBar = true;
- initPerformanceBar(jsPeek);
- }
-}
-
-// If js-peek is not loaded when this script is executed, this call will do nothing
-// If this is the case, then it will loadBar on DOMContentLoaded. We would prefer it
-// to be initialized before the DOMContetLoaded event in order to pick up all the
-// requests sent from the page.
-loadBar();
-
-document.addEventListener('DOMContentLoaded', () => {
- loadBar();
-});
-
+initPerformanceBar(document.querySelector('#js-peek'));
initPerformanceBarLog();
export default initPerformanceBar;
diff --git a/app/assets/javascripts/rest_api.js b/app/assets/javascripts/rest_api.js
new file mode 100644
index 00000000000..ea8f87001f0
--- /dev/null
+++ b/app/assets/javascripts/rest_api.js
@@ -0,0 +1,15 @@
+export * from './api/groups_api';
+export * from './api/projects_api';
+export * from './api/user_api';
+
+// Note: It's not possible to spy on methods imported from this file in
+// Jest tests. See https://stackoverflow.com/a/53307822/1063392.
+// As a workaround, in Jest tests, import the methods from the file
+// in which they are defined:
+//
+// import * as UserApi from '~/api/user_api';
+// vs...
+// import * as UserApi from '~/rest_api';
+//
+// // This will only work with option #2 above.
+// jest.spyOn(UserApi, 'getUsers')
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index 30e0e552165..c8efbd73b48 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -6,7 +6,7 @@ import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import { GlToast, GlModal, GlTooltipDirective, GlIcon, GlFormCheckbox } from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __, s__ } from '~/locale';
-import Api from '~/api';
+import { updateUserStatus } from '~/rest_api';
import EmojiMenuInModal from './emoji_menu_in_modal';
import { isUserBusy, isValidAvailibility } from './utils';
import * as Emoji from '~/emoji';
@@ -163,7 +163,7 @@ export default {
setStatus() {
const { emoji, message, availability } = this;
- Api.postUserStatus({
+ updateUserStatus({
emoji,
message,
availability: availability ? AVAILABILITY_STATUS.BUSY : AVAILABILITY_STATUS.NOT_SET,
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 4beaa2509d9..2653e1351ed 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -191,9 +191,13 @@ export default {
mergeError = mergeError.slice(0, -1);
}
- return sprintf(s__('mrWidget|Merge failed: %{mergeError}. Please try again.'), {
- mergeError,
- });
+ return sprintf(
+ s__('mrWidget|Merge failed: %{mergeError}. Please try again.'),
+ {
+ mergeError,
+ },
+ false,
+ );
},
shouldShowAccessibilityReport() {
return this.mr.accessibilityReportPath;
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index c4564478462..39718793c1d 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -57,6 +57,10 @@ class Admin::ProjectsController < Admin::ApplicationController
namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
+ if @project.errors[:new_namespace].present?
+ flash[:alert] = @project.errors[:new_namespace].first
+ end
+
@project.reset
redirect_to admin_project_path(@project)
end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index ade698baa7f..857f36e3833 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -4,7 +4,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
include Gitlab::Experimentation::ControllerConcern
include InitializesCurrentUserMode
- before_action :verify_confirmed_email!
+ before_action :verify_confirmed_email!, :verify_confidential_application!
layout 'profile'
@@ -24,18 +24,19 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
end
end
- def create
- # Confidential apps require the client_secret to be sent with the request.
- # Doorkeeper allows implicit grant flow requests (response_type=token) to
- # work without client_secret regardless of the confidential setting.
- if pre_auth.authorizable? && pre_auth.response_type == 'token' && pre_auth.client.application.confidential
- render "doorkeeper/authorizations/error"
- else
- super
- end
+ private
+
+ # Confidential apps require the client_secret to be sent with the request.
+ # Doorkeeper allows implicit grant flow requests (response_type=token) to
+ # work without client_secret regardless of the confidential setting.
+ # This leads to security vulnerabilities and we want to block it.
+ def verify_confidential_application!
+ render 'doorkeeper/authorizations/error' if authorizable_confidential?
end
- private
+ def authorizable_confidential?
+ pre_auth.authorizable? && pre_auth.response_type == 'token' && pre_auth.client.application.confidential
+ end
def verify_confirmed_email!
return if current_user&.confirmed?
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index e3d82e7a091..8920133734c 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -159,7 +159,7 @@ module PageLayoutHelper
end
def user_status_properties(user)
- default_properties = { current_emoji: '', current_message: '', can_set_user_availability: Feature.enabled?(:set_user_availability_status, user), default_emoji: UserStatus::DEFAULT_EMOJI }
+ default_properties = { current_emoji: '', current_message: '', can_set_user_availability: Feature.enabled?(:set_user_availability_status, user, default_enabled: :yaml), default_emoji: UserStatus::DEFAULT_EMOJI }
return default_properties unless user&.status
default_properties.merge({
diff --git a/app/models/ci/group.rb b/app/models/ci/group.rb
index f9201446931..c7c0ec61e62 100644
--- a/app/models/ci/group.rb
+++ b/app/models/ci/group.rb
@@ -24,9 +24,22 @@ module Ci
def status
strong_memoize(:status) do
+ status_struct.status
+ end
+ end
+
+ def success?
+ status.to_s == 'success'
+ end
+
+ def has_warnings?
+ status_struct.warnings?
+ end
+
+ def status_struct
+ strong_memoize(:status_struct) do
Gitlab::Ci::Status::Composite
.new(@jobs)
- .status
end
end
diff --git a/app/models/concerns/boards/listable.rb b/app/models/concerns/boards/listable.rb
index 013669a1444..b7c0a8b3489 100644
--- a/app/models/concerns/boards/listable.rb
+++ b/app/models/concerns/boards/listable.rb
@@ -34,7 +34,13 @@ module Boards
end
def title
- label? ? label.name : list_type.humanize
+ if label?
+ label.name
+ elsif backlog?
+ _('Open')
+ else
+ list_type.humanize
+ end
end
private
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 1f4abfc1aca..dafd3d095ec 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -157,8 +157,12 @@ class JiraService < IssueTrackerService
# support any events.
end
+ def find_issue(issue_key)
+ jira_request { client.Issue.find(issue_key) }
+ end
+
def close_issue(entity, external_issue)
- issue = jira_request { client.Issue.find(external_issue.iid) }
+ issue = find_issue(external_issue.iid)
return if issue.nil? || has_resolution?(issue) || !jira_issue_transition_id.present?
@@ -172,7 +176,7 @@ class JiraService < IssueTrackerService
# Depending on the Jira project's workflow, a comment during transition
# may or may not be allowed. Refresh the issue after transition and check
# if it is closed, so we don't have one comment for every commit.
- issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
+ issue = find_issue(issue.key) if transition_issue(issue)
add_issue_solved_comment(issue, commit_id, commit_url) if has_resolution?(issue)
end
@@ -181,7 +185,7 @@ class JiraService < IssueTrackerService
return s_("JiraService|Events for %{noteable_model_name} are disabled.") % { noteable_model_name: noteable.model_name.plural.humanize(capitalize: false) }
end
- jira_issue = jira_request { client.Issue.find(mentioned.id) }
+ jira_issue = find_issue(mentioned.id)
return unless jira_issue.present?
diff --git a/app/services/ci/destroy_expired_job_artifacts_service.rb b/app/services/ci/destroy_expired_job_artifacts_service.rb
index 4f5a3f7868f..7d8a3c17abe 100644
--- a/app/services/ci/destroy_expired_job_artifacts_service.rb
+++ b/app/services/ci/destroy_expired_job_artifacts_service.rb
@@ -12,6 +12,10 @@ module Ci
EXCLUSIVE_LOCK_KEY = 'expired_job_artifacts:destroy:lock'
LOCK_TIMEOUT = 6.minutes
+ def initialize
+ @removed_artifacts_count = 0
+ end
+
##
# Destroy expired job artifacts on GitLab instance
#
@@ -20,47 +24,13 @@ module Ci
# which is scheduled every 7 minutes.
def execute
in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
- if ::Feature.enabled?(:ci_slow_artifacts_removal)
- destroy_job_and_pipeline_artifacts
- else
- loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
- destroy_artifacts_batch
- end
- end
+ destroy_job_artifacts_with_slow_iteration(Time.current)
end
- end
-
- private
-
- def destroy_job_and_pipeline_artifacts
- start_at = Time.current
- destroy_job_artifacts_with_slow_iteration(start_at)
- timeout = LOOP_TIMEOUT - (Time.current - start_at)
- return false if timeout < 0
-
- loop_until(timeout: timeout, limit: LOOP_LIMIT) do
- destroy_pipeline_artifacts_batch
- end
+ @removed_artifacts_count
end
- def destroy_artifacts_batch
- destroy_job_artifacts_batch || destroy_pipeline_artifacts_batch
- end
-
- def destroy_job_artifacts_batch
- artifacts = Ci::JobArtifact
- .expired(BATCH_SIZE)
- .unlocked
- .order_expired_desc
- .with_destroy_preloads
- .to_a
-
- return false if artifacts.empty?
-
- parallel_destroy_batch(artifacts)
- true
- end
+ private
def destroy_job_artifacts_with_slow_iteration(start_at)
Ci::JobArtifact.expired_before(start_at).each_batch(of: BATCH_SIZE, column: :expire_at, order: :desc) do |relation, index|
@@ -72,19 +42,6 @@ module Ci
end
end
- # TODO: Make sure this can also be parallelized
- # https://gitlab.com/gitlab-org/gitlab/-/issues/270973
- def destroy_pipeline_artifacts_batch
- return false if ::Feature.enabled?(:ci_split_pipeline_artifacts_removal)
-
- artifacts = Ci::PipelineArtifact.expired(BATCH_SIZE).to_a
- return false if artifacts.empty?
-
- artifacts.each(&:destroy!)
-
- true
- end
-
def parallel_destroy_batch(job_artifacts)
Ci::DeletedObject.transaction do
Ci::DeletedObject.bulk_import(job_artifacts)
@@ -93,14 +50,14 @@ module Ci
end
# This is executed outside of the transaction because it depends on Redis
- update_statistics_for(job_artifacts)
- destroyed_artifacts_counter.increment({}, job_artifacts.size)
+ update_project_statistics_for(job_artifacts)
+ increment_monitoring_statistics(job_artifacts.size)
end
# This method is implemented in EE and it must do only database work
def destroy_related_records_for(job_artifacts); end
- def update_statistics_for(job_artifacts)
+ def update_project_statistics_for(job_artifacts)
artifacts_by_project = job_artifacts.group_by(&:project)
artifacts_by_project.each do |project, artifacts|
delta = -artifacts.sum { |artifact| artifact.size.to_i }
@@ -109,6 +66,11 @@ module Ci
end
end
+ def increment_monitoring_statistics(size)
+ destroyed_artifacts_counter.increment({}, size)
+ @removed_artifacts_count += size
+ end
+
def destroyed_artifacts_counter
strong_memoize(:destroyed_artifacts_counter) do
name = :destroyed_job_artifacts_count_total
diff --git a/app/services/draft_notes/base_service.rb b/app/services/draft_notes/base_service.rb
index 89daae0e8f4..95c291ea800 100644
--- a/app/services/draft_notes/base_service.rb
+++ b/app/services/draft_notes/base_service.rb
@@ -8,6 +8,10 @@ module DraftNotes
@merge_request, @current_user, @params = merge_request, current_user, params.dup
end
+ def merge_request_activity_counter
+ Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter
+ end
+
private
def draft_notes
diff --git a/app/services/draft_notes/create_service.rb b/app/services/draft_notes/create_service.rb
index 501778b7d5f..5ff971b66c1 100644
--- a/app/services/draft_notes/create_service.rb
+++ b/app/services/draft_notes/create_service.rb
@@ -31,6 +31,10 @@ module DraftNotes
merge_request.diffs.clear_cache
end
+ if draft_note.persisted?
+ merge_request_activity_counter.track_create_review_note_action(user: current_user)
+ end
+
draft_note
end
diff --git a/app/services/draft_notes/publish_service.rb b/app/services/draft_notes/publish_service.rb
index a9a7304e5ed..316abff4552 100644
--- a/app/services/draft_notes/publish_service.rb
+++ b/app/services/draft_notes/publish_service.rb
@@ -9,6 +9,7 @@ module DraftNotes
publish_draft_note(draft)
else
publish_draft_notes
+ merge_request_activity_counter.track_publish_review_action(user: current_user)
end
success
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index bf9f1336a4f..41699d6f01f 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -70,7 +70,7 @@
prepend: emoji_button,
append: reset_message_button,
placeholder: s_("Profiles|What's your status?")
- - if Feature.enabled?(:set_user_availability_status, @user)
+ - if Feature.enabled?(:set_user_availability_status, @user, default_enabled: :yaml)
.checkbox-icon-inline-wrapper
= status_form.check_box :availability, { data: { testid: "user-availability-checkbox" }, label: s_("Profiles|Busy"), wrapper_class: 'gl-mr-0 gl-font-weight-bold' }, availability["busy"], availability["not_set"]
.gl-text-gray-600.gl-ml-5= s_('Profiles|"Busy" will be shown next to your name')
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 5b9f868a71a..40faf91eadf 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,3 +1,5 @@
+- current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1]
+- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
- @content_class = "limit-container-width" unless fluid_layout
- @skip_current_level_breadcrumb = true
diff --git a/app/views/shared/empty_states/_snippets.html.haml b/app/views/shared/empty_states/_snippets.html.haml
index db8da50d868..aa762782c46 100644
--- a/app/views/shared/empty_states/_snippets.html.haml
+++ b/app/views/shared/empty_states/_snippets.html.haml
@@ -1,6 +1,6 @@
- button_path = local_assigns.fetch(:button_path, false)
-.row.empty-state.mt-0
+.row.empty-state
.col-12
.svg-content
= image_tag 'illustrations/snippets_empty.svg', data: { qa_selector: 'svg_content' }
@@ -16,5 +16,3 @@
= link_to s_('SnippetsEmptyState|Documentation'), help_page_path('user/snippets.md'), class: 'btn btn-default', title: s_('SnippetsEmptyState|Documentation')
- else
%h4.text-center= s_('SnippetsEmptyState|There are no snippets to show.')
-
-
diff --git a/app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb b/app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb
index 6a02c980c99..0bb911bc6c8 100644
--- a/app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb
+++ b/app/workers/ci/pipeline_artifacts/expire_artifacts_worker.rb
@@ -14,8 +14,6 @@ module Ci
feature_category :continuous_integration
def perform
- return unless ::Feature.enabled?(:ci_split_pipeline_artifacts_removal)
-
service = ::Ci::PipelineArtifacts::DestroyExpiredArtifactsService.new
artifacts_count = service.execute
log_extra_metadata_on_done(:destroyed_pipeline_artifacts_count, artifacts_count)
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index 12372961250..5db9f0b67e0 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -10,6 +10,8 @@ class ExpireBuildArtifactsWorker # rubocop:disable Scalability/IdempotentWorker
feature_category :continuous_integration
def perform
- Ci::DestroyExpiredJobArtifactsService.new.execute
+ service = Ci::DestroyExpiredJobArtifactsService.new
+ artifacts_count = service.execute
+ log_extra_metadata_on_done(:destroyed_job_artifacts_count, artifacts_count)
end
end
diff --git a/changelogs/unreleased/243857-matrix-job-status-allow-failure.yml b/changelogs/unreleased/243857-matrix-job-status-allow-failure.yml
new file mode 100644
index 00000000000..13521b63173
--- /dev/null
+++ b/changelogs/unreleased/243857-matrix-job-status-allow-failure.yml
@@ -0,0 +1,5 @@
+---
+title: Correct status indicator for jobs groups when failure is allowed
+merge_request: 51478
+author: Sune Keller (sirlatrom)
+type: fixed
diff --git a/changelogs/unreleased/270059-feature-flag-rollout-of-ci_pipeline_editor_page.yml b/changelogs/unreleased/270059-feature-flag-rollout-of-ci_pipeline_editor_page.yml
new file mode 100644
index 00000000000..1e50573f857
--- /dev/null
+++ b/changelogs/unreleased/270059-feature-flag-rollout-of-ci_pipeline_editor_page.yml
@@ -0,0 +1,5 @@
+---
+title: Enables the CI Pipeline Editor feature as a way to edit the GitLab CI/CD configuration
+merge_request: 51484
+author:
+type: added
diff --git a/changelogs/unreleased/284640-fix-newline-in-custom-server-hook-renders-improperly.yml b/changelogs/unreleased/284640-fix-newline-in-custom-server-hook-renders-improperly.yml
new file mode 100644
index 00000000000..ceda863f4db
--- /dev/null
+++ b/changelogs/unreleased/284640-fix-newline-in-custom-server-hook-renders-improperly.yml
@@ -0,0 +1,5 @@
+---
+title: Fix multiple errors in custom server hook render improperly
+merge_request: 51001
+author: Kev @KevSlashNull
+type: fixed
diff --git a/changelogs/unreleased/292822-track-comment-related-metrics-for-a-merge-request-3.yml b/changelogs/unreleased/292822-track-comment-related-metrics-for-a-merge-request-3.yml
new file mode 100644
index 00000000000..18257fb475e
--- /dev/null
+++ b/changelogs/unreleased/292822-track-comment-related-metrics-for-a-merge-request-3.yml
@@ -0,0 +1,5 @@
+---
+title: Add metrics to starting and publishing a review
+merge_request: 51521
+author:
+type: other
diff --git a/changelogs/unreleased/30477-vfazio-alert-transfer-error.yml b/changelogs/unreleased/30477-vfazio-alert-transfer-error.yml
new file mode 100644
index 00000000000..7cf2f9d3d0f
--- /dev/null
+++ b/changelogs/unreleased/30477-vfazio-alert-transfer-error.yml
@@ -0,0 +1,5 @@
+---
+title: Flash transfer errors in the admin project controller
+merge_request: 50541
+author: Vincent Fazio
+type: fixed
diff --git a/changelogs/unreleased/ci-pipeline-artifacts-removal-cleanup.yml b/changelogs/unreleased/ci-pipeline-artifacts-removal-cleanup.yml
new file mode 100644
index 00000000000..e6f2ecbe09b
--- /dev/null
+++ b/changelogs/unreleased/ci-pipeline-artifacts-removal-cleanup.yml
@@ -0,0 +1,6 @@
+---
+title: Extract expired pipeline artifacts removal service into it's own background
+ worker
+merge_request: 51323
+author:
+type: changed
diff --git a/changelogs/unreleased/default-enable-set-user-availability-status.yml b/changelogs/unreleased/default-enable-set-user-availability-status.yml
new file mode 100644
index 00000000000..2bce3a1605f
--- /dev/null
+++ b/changelogs/unreleased/default-enable-set-user-availability-status.yml
@@ -0,0 +1,5 @@
+---
+title: Default enable set_user_availability_status
+merge_request: 51668
+author:
+type: changed
diff --git a/changelogs/unreleased/doc-218997-fj-add-doc-for-snippet-api-repository-moves.yml b/changelogs/unreleased/doc-218997-fj-add-doc-for-snippet-api-repository-moves.yml
new file mode 100644
index 00000000000..5d916009525
--- /dev/null
+++ b/changelogs/unreleased/doc-218997-fj-add-doc-for-snippet-api-repository-moves.yml
@@ -0,0 +1,5 @@
+---
+title: Add documentation for new Snippet repository storage move API
+merge_request: 50151
+author:
+type: other
diff --git a/changelogs/unreleased/ph-285635-suggestionAppliedResolvedStatus.yml b/changelogs/unreleased/ph-285635-suggestionAppliedResolvedStatus.yml
new file mode 100644
index 00000000000..0a1e0b8aea4
--- /dev/null
+++ b/changelogs/unreleased/ph-285635-suggestionAppliedResolvedStatus.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed applied message showing before discussion gets resolved
+merge_request: 51605
+author:
+type: fixed
diff --git a/changelogs/unreleased/rename-backlog-list-to-open-in-backend.yml b/changelogs/unreleased/rename-backlog-list-to-open-in-backend.yml
new file mode 100644
index 00000000000..70a4ed3e093
--- /dev/null
+++ b/changelogs/unreleased/rename-backlog-list-to-open-in-backend.yml
@@ -0,0 +1,5 @@
+---
+title: Rename Backlog list to Open in issue boards
+merge_request: 51562
+author:
+type: fixed
diff --git a/changelogs/unreleased/terraform-0-14-cache.yml b/changelogs/unreleased/terraform-0-14-cache.yml
new file mode 100644
index 00000000000..af937293768
--- /dev/null
+++ b/changelogs/unreleased/terraform-0-14-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Update Terraform Pipline templaes to support 0.14 lockfile cache
+merge_request: 50647
+author: Aurelian Shuttleworth
+type: fixed
diff --git a/changelogs/unreleased/yo-master-patch-32789.yml b/changelogs/unreleased/yo-master-patch-32789.yml
new file mode 100644
index 00000000000..fe5291eaffa
--- /dev/null
+++ b/changelogs/unreleased/yo-master-patch-32789.yml
@@ -0,0 +1,5 @@
+---
+title: Fix slack application helper card
+merge_request: 51034
+author: Yogi (@yo)
+type: fixed
diff --git a/changelogs/unreleased/yo-master-patch-66129.yml b/changelogs/unreleased/yo-master-patch-66129.yml
new file mode 100644
index 00000000000..b5aead2b52f
--- /dev/null
+++ b/changelogs/unreleased/yo-master-patch-66129.yml
@@ -0,0 +1,5 @@
+---
+title: Remove margin top for snippets empty state
+merge_request: 51038
+author: Yogi (@yo)
+type: fixed
diff --git a/config/feature_flags/development/ci_pipeline_editor_page.yml b/config/feature_flags/development/ci_pipeline_editor_page.yml
index 9870f419842..313126f3444 100644
--- a/config/feature_flags/development/ci_pipeline_editor_page.yml
+++ b/config/feature_flags/development/ci_pipeline_editor_page.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/270059
milestone: '13.6'
type: development
group: group::pipeline authoring
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/ci_slow_artifacts_removal.yml b/config/feature_flags/development/ci_slow_artifacts_removal.yml
deleted file mode 100644
index a255a6cff1f..00000000000
--- a/config/feature_flags/development/ci_slow_artifacts_removal.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_slow_artifacts_removal
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47496
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/281688
-milestone: '13.8'
-type: development
-group: 'group::continuous integration'
-default_enabled: false
diff --git a/config/feature_flags/development/ci_split_pipeline_artifacts_removal.yml b/config/feature_flags/development/ci_split_pipeline_artifacts_removal.yml
deleted file mode 100644
index be56931a331..00000000000
--- a/config/feature_flags/development/ci_split_pipeline_artifacts_removal.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_split_pipeline_artifacts_removal
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50446
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/295300
-milestone: '13.8'
-type: development
-group: group::continuous integration
-default_enabled: false
diff --git a/config/feature_flags/development/set_user_availability_status.yml b/config/feature_flags/development/set_user_availability_status.yml
index be3ff522ccc..dd48ddef6d5 100644
--- a/config/feature_flags/development/set_user_availability_status.yml
+++ b/config/feature_flags/development/set_user_availability_status.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/281073
milestone: '13.6'
type: development
group: group::optimize
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/usage_data_i_code_review_user_create_review_note.yml b/config/feature_flags/development/usage_data_i_code_review_user_create_review_note.yml
new file mode 100644
index 00000000000..fdc3b4569ef
--- /dev/null
+++ b/config/feature_flags/development/usage_data_i_code_review_user_create_review_note.yml
@@ -0,0 +1,8 @@
+---
+name: usage_data_i_code_review_user_create_review_note
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51521
+rollout_issue_url:
+milestone: '13.8'
+type: development
+group: group::code review
+default_enabled: true
diff --git a/config/feature_flags/development/usage_data_i_code_review_user_publish_review.yml b/config/feature_flags/development/usage_data_i_code_review_user_publish_review.yml
new file mode 100644
index 00000000000..079ceb023f4
--- /dev/null
+++ b/config/feature_flags/development/usage_data_i_code_review_user_publish_review.yml
@@ -0,0 +1,8 @@
+---
+name: usage_data_i_code_review_user_publish_review
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51351
+rollout_issue_url:
+milestone: '13.8'
+type: development
+group: group::code review
+default_enabled: true
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 3fd9a021b4b..fe8b3e5f566 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -1363,7 +1363,7 @@ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.t
If your GitLab instance already has repositories on single Gitaly nodes, these aren't migrated to
Gitaly Cluster automatically.
-Repositories may be moved from one storage location using the [Project repository storage moves API](../../api/project_repository_storage_moves.md):
+Project repositories may be moved from one storage location using the [Project repository storage moves API](../../api/project_repository_storage_moves.md):
NOTE:
The Project repository storage moves API [cannot move all repository types](../../api/project_repository_storage_moves.md#limitations).
@@ -1387,6 +1387,8 @@ To move repositories to Gitaly Cluster:
using the API to confirm that all projects have moved. No projects should be returned
with `repository_storage` field set to the old storage.
+In a similar way, you can move Snippet repositories using the [Snippet repository storage moves API](../../api/snippet_repository_storage_moves.md):
+
## Debugging Praefect
If you receive an error, check `/var/log/gitlab/gitlab-rails/production.log`.
diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md
index 7cc15f9cea4..029f3bb01ed 100644
--- a/doc/administration/operations/moving_repositories.md
+++ b/doc/administration/operations/moving_repositories.md
@@ -23,12 +23,14 @@ For more information, see:
- [Configuring additional storage for Gitaly](../gitaly/index.md#network-architecture). Within this
example, additional storage called `storage1` and `storage2` is configured.
- [The API documentation](../../api/project_repository_storage_moves.md) details the endpoints for
- querying and scheduling repository moves.
+ querying and scheduling project repository moves.
+- [The API documentation](../../api/snippet_repository_storage_moves.md) details the endpoints for
+ querying and scheduling snippet repository moves.
- [Migrate existing repositories to Gitaly Cluster](../gitaly/praefect.md#migrate-existing-repositories-to-gitaly-cluster).
### Limitations
-Read more in the [API documentation](../../api/project_repository_storage_moves.md#limitations).
+Read more in the [API documentation for projects](../../api/project_repository_storage_moves.md#limitations) and the [API documentation for snippets](../../api/snippet_repository_storage_moves.md#limitations).
## Migrating to another GitLab instance
diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md
index c6fd5b7c45c..029e434749f 100644
--- a/doc/api/api_resources.md
+++ b/doc/api/api_resources.md
@@ -158,6 +158,7 @@ The following API resources are available outside of project and group contexts
| [Runners](runners.md) | `/runners` (also available for projects) |
| [Search](search.md) | `/search` (also available for groups and projects) |
| [Settings](settings.md) **(CORE ONLY)** | `/application/settings` |
+| [Snippet repository storage moves](snippet_repository_storage_moves.md) **(CORE ONLY)** | `/snippet_repository_storage_moves` |
| [Statistics](statistics.md) | `/application/statistics` |
| [Sidekiq metrics](sidekiq_metrics.md) **(CORE ONLY)** | `/sidekiq` |
| [Suggestions](suggestions.md) | `/suggestions` |
diff --git a/doc/api/project_repository_storage_moves.md b/doc/api/project_repository_storage_moves.md
index 07743a48654..52c357ca32a 100644
--- a/doc/api/project_repository_storage_moves.md
+++ b/doc/api/project_repository_storage_moves.md
@@ -30,9 +30,10 @@ read-only. Please try again later.` message if they try to push new commits.
This API requires you to [authenticate yourself](README.md#authentication) as an administrator.
+Snippet repositories can be moved using the [Snippet repository storage moves API](snippet_repository_storage_moves.md).
+
## Limitations
-- The repositories associated with snippets [can't be moved with the API](https://gitlab.com/groups/gitlab-org/-/epics/3393).
- Group-level wikis [can't be moved with the API](https://gitlab.com/gitlab-org/gitlab/-/issues/219003).
## Retrieve all project repository storage moves
diff --git a/doc/api/snippet_repository_storage_moves.md b/doc/api/snippet_repository_storage_moves.md
new file mode 100644
index 00000000000..f60b1dfb449
--- /dev/null
+++ b/doc/api/snippet_repository_storage_moves.md
@@ -0,0 +1,293 @@
+---
+stage: Create
+group: Editor
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+type: reference
+---
+
+# Snippet repository storage moves API **(CORE ONLY)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49228) in GitLab 13.8.
+
+Snippet repositories can be moved between storages. This can be useful when
+[migrating to Gitaly Cluster](../administration/gitaly/praefect.md#migrate-existing-repositories-to-gitaly-cluster),
+for example.
+
+As snippet repository storage moves are processed, they transition through different states. Values
+of `state` are:
+
+- `initial`
+- `scheduled`
+- `started`
+- `finished`
+- `failed`
+- `replicated`
+- `cleanup failed`
+
+To ensure data integrity, snippets are put in a temporary read-only state for the
+duration of the move. During this time, users receive a `The repository is temporarily
+read-only. Please try again later.` message if they try to push new commits.
+
+This API requires you to [authenticate yourself](README.md#authentication) as an administrator.
+
+Project repositories can be moved using the [Project repository storage moves API](project_repository_storage_moves.md).
+
+## Limitations
+
+- Group-level wikis [can't be moved with the API](https://gitlab.com/gitlab-org/gitlab/-/issues/219003).
+
+## Retrieve all snippet repository storage moves
+
+```plaintext
+GET /snippet_repository_storage_moves
+```
+
+By default, `GET` requests return 20 results at a time because the API results
+are [paginated](README.md#pagination).
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/snippet_repository_storage_moves"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "created_at": "2020-05-07T04:27:17.234Z",
+ "state": "scheduled",
+ "source_storage_name": "default",
+ "destination_storage_name": "storage2",
+ "snippet": {
+ "id": 65,
+ "title": "Test Snippet",
+ "description": null,
+ "visibility": "internal",
+ "updated_at": "2020-12-01T11:15:50.385Z",
+ "created_at": "2020-12-01T11:15:50.385Z",
+ "project_id": null,
+ "web_url": "https://gitlab.example.com/-/snippets/65",
+ "raw_url": "https://gitlab.example.com/-/snippets/65/raw",
+ "ssh_url_to_repo": "ssh://user@gitlab.example.com/snippets/65.git",
+ "http_url_to_repo": "https://gitlab.example.com/snippets/65.git"
+ }
+ }
+]
+```
+
+## Retrieve all repository storage moves for a snippet
+
+```plaintext
+GET /snippets/:snippet_id/repository_storage_moves
+```
+
+By default, `GET` requests return 20 results at a time because the API results
+are [paginated](README.md#pagination).
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `snippet_id` | integer | yes | ID of the snippet. |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/snippets/1/repository_storage_moves"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "created_at": "2020-05-07T04:27:17.234Z",
+ "state": "scheduled",
+ "source_storage_name": "default",
+ "destination_storage_name": "storage2",
+ "snippet": {
+ "id": 65,
+ "title": "Test Snippet",
+ "description": null,
+ "visibility": "internal",
+ "updated_at": "2020-12-01T11:15:50.385Z",
+ "created_at": "2020-12-01T11:15:50.385Z",
+ "project_id": null,
+ "web_url": "https://gitlab.example.com/-/snippets/65",
+ "raw_url": "https://gitlab.example.com/-/snippets/65/raw",
+ "ssh_url_to_repo": "ssh://user@gitlab.example.com/snippets/65.git",
+ "http_url_to_repo": "https://gitlab.example.com/snippets/65.git"
+ }
+ }
+]
+```
+
+## Get a single snippet repository storage move
+
+```plaintext
+GET /snippet_repository_storage_moves/:repository_storage_id
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `repository_storage_id` | integer | yes | ID of the snippet repository storage move. |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/snippet_repository_storage_moves/1"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "created_at": "2020-05-07T04:27:17.234Z",
+ "state": "scheduled",
+ "source_storage_name": "default",
+ "destination_storage_name": "storage2",
+ "snippet": {
+ "id": 65,
+ "title": "Test Snippet",
+ "description": null,
+ "visibility": "internal",
+ "updated_at": "2020-12-01T11:15:50.385Z",
+ "created_at": "2020-12-01T11:15:50.385Z",
+ "project_id": null,
+ "web_url": "https://gitlab.example.com/-/snippets/65",
+ "raw_url": "https://gitlab.example.com/-/snippets/65/raw",
+ "ssh_url_to_repo": "ssh://user@gitlab.example.com/snippets/65.git",
+ "http_url_to_repo": "https://gitlab.example.com/snippets/65.git"
+ }
+}
+```
+
+## Get a single repository storage move for a snippet
+
+```plaintext
+GET /snippets/:snippet_id/repository_storage_moves/:repository_storage_id
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `snippet_id` | integer | yes | ID of the snippet. |
+| `repository_storage_id` | integer | yes | ID of the snippet repository storage move. |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/snippets/1/repository_storage_moves/1"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "created_at": "2020-05-07T04:27:17.234Z",
+ "state": "scheduled",
+ "source_storage_name": "default",
+ "destination_storage_name": "storage2",
+ "snippet": {
+ "id": 65,
+ "title": "Test Snippet",
+ "description": null,
+ "visibility": "internal",
+ "updated_at": "2020-12-01T11:15:50.385Z",
+ "created_at": "2020-12-01T11:15:50.385Z",
+ "project_id": null,
+ "web_url": "https://gitlab.example.com/-/snippets/65",
+ "raw_url": "https://gitlab.example.com/-/snippets/65/raw",
+ "ssh_url_to_repo": "ssh://user@gitlab.example.com/snippets/65.git",
+ "http_url_to_repo": "https://gitlab.example.com/snippets/65.git"
+ }
+}
+```
+
+## Schedule a repository storage move for a snippet
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49228) in GitLab 13.8.
+
+```plaintext
+POST /snippets/:snippet_id/repository_storage_moves
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `snippet_id` | integer | yes | ID of the snippet. |
+| `destination_storage_name` | string | no | Name of the destination storage shard. In [GitLab 13.5 and later](https://gitlab.com/gitlab-org/gitaly/-/issues/3209), the storage is selected automatically if not provided. |
+
+Example request:
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
+--data '{"destination_storage_name":"storage2"}' "https://gitlab.example.com/api/v4/snippets/1/repository_storage_moves"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "created_at": "2020-05-07T04:27:17.234Z",
+ "state": "scheduled",
+ "source_storage_name": "default",
+ "destination_storage_name": "storage2",
+ "snippet": {
+ "id": 65,
+ "title": "Test Snippet",
+ "description": null,
+ "visibility": "internal",
+ "updated_at": "2020-12-01T11:15:50.385Z",
+ "created_at": "2020-12-01T11:15:50.385Z",
+ "project_id": null,
+ "web_url": "https://gitlab.example.com/-/snippets/65",
+ "raw_url": "https://gitlab.example.com/-/snippets/65/raw",
+ "ssh_url_to_repo": "ssh://user@gitlab.example.com/snippets/65.git",
+ "http_url_to_repo": "https://gitlab.example.com/snippets/65.git"
+ }
+}
+```
+
+## Schedule repository storage moves for all snippets on a storage shard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49228) in GitLab 13.8.
+
+Schedules repository storage moves for each snippet repository stored on the source storage shard.
+
+```plaintext
+POST /snippet_repository_storage_moves
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `source_storage_name` | string | yes | Name of the source storage shard. |
+| `destination_storage_name` | string | no | Name of the destination storage shard. The storage is selected automatically if not provided. |
+
+Example request:
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
+--data '{"source_storage_name":"default"}' "https://gitlab.example.com/api/v4/snippet_repository_storage_moves"
+```
+
+Example response:
+
+```json
+{
+ "message": "202 Accepted"
+}
+```
diff --git a/doc/ci/pipeline_editor/index.md b/doc/ci/pipeline_editor/index.md
index 5e7802feae4..2d03ceb264f 100644
--- a/doc/ci/pipeline_editor/index.md
+++ b/doc/ci/pipeline_editor/index.md
@@ -8,8 +8,8 @@ type: reference
# Pipeline Editor **(CORE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4540) in GitLab 13.8.
-> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
-> - It's disabled on GitLab.com.
+> - It's [deployed behind a feature flag](../../user/feature_flags.md), enabled by default.
+> - It's enabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-pipeline-editor). **(CORE ONLY)**
@@ -115,18 +115,18 @@ checkbox appears. Select it to start a new merge request after you commit the ch
## Enable or disable pipeline editor **(CORE ONLY)**
The pipeline editor is under development and not ready for production use. It is
-deployed behind a feature flag that is **disabled by default**.
+deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
-can enable it.
+can disable it.
-To enable it:
+To disable it:
```ruby
-Feature.enable(:ci_pipeline_editor_page)
+Feature.disable(:ci_pipeline_editor_page)
```
-To disable it:
+To enable it:
```ruby
-Feature.disable(:ci_pipeline_editor_page)
+Feature.enable(:ci_pipeline_editor_page)
```
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 4cee364b209..992dfb98e40 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -339,6 +339,7 @@ experience, refactors the existing code). Then:
convey your intent.
- For non-mandatory suggestions, decorate with (non-blocking) so the author knows they can
optionally resolve within the merge request or follow-up at a later stage.
+ - There's a [Chrome/Firefox addon](https://gitlab.com/conventionalcomments/conventional-comments-button) which you can use to apply [Conventional Comment](https://conventionalcomments.org/) prefixes.
- After a round of line notes, it can be helpful to post a summary note such as
"Looks good to me", or "Just a couple things to address."
- Assign the merge request to the author if changes are required following your
diff --git a/doc/development/fe_guide/editor_lite.md b/doc/development/fe_guide/editor_lite.md
index 465d64ff63c..47ef85d8737 100644
--- a/doc/development/fe_guide/editor_lite.md
+++ b/doc/development/fe_guide/editor_lite.md
@@ -104,7 +104,14 @@ someActionFunction() {
## Extensions
-Editor Lite has been built to provide a universal, extensible editing tool to the whole product, which would not depend on any particular group. Even though the Editor Lite's core is owned by [Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor-fe/), the main functional elements — extensions — can be owned by any group. Editor Lite extensions' main idea is that the core of the editor remains very slim and stable. At the same time, whatever new functionality is needed can be added as an extension to this core, without touching the core itself. It allows any group to build and own any new editing functionality without being afraid of it being broken or overridden with the Editor Lite changes.
+Editor Lite has been built to provide a universal, extensible editing tool to the whole product,
+which would not depend on any particular group. Even though the Editor Lite's core is owned by
+[Create::Editor FE Team](https://about.gitlab.com/handbook/engineering/development/dev/create-editor/),
+the main functional elements — extensions — can be owned by any group. Editor Lite extensions' main idea
+is that the core of the editor remains very slim and stable. At the same time, whatever new functionality
+is needed can be added as an extension to this core, without touching the core itself. It allows any group
+to build and own any new editing functionality without being afraid of it being broken or overridden with
+the Editor Lite changes.
Structurally, the complete implementation of Editor Lite could be presented as the following diagram:
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 545cb8045aa..a96975fea92 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -203,11 +203,12 @@ If you previously selected the "Busy" checkbox, remember to deselect it when you
## Busy status indicator
-> - Introduced in GitLab 13.6.
-> - It's [deployed behind a feature flag](../feature_flags.md), disabled by default.
-> - It's disabled on GitLab.com.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259649) in GitLab 13.6.
+> - It was [deployed behind a feature flag](../feature_flags.md), disabled by default.
+> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/281073) in GitLab 13.8.
+> - It's enabled on GitLab.com.
> - It's not recommended for production use.
-> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-busy-status-feature).
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-busy-status-feature).
To indicate to others that you are busy, you can set an indicator
@@ -228,10 +229,16 @@ To set the busy status indicator, either:
1. Click **Edit profile** (**{pencil}**).
1. Select the **Busy** checkbox
-### Enable busy status feature
+### Disable busy status feature
-The busy status feature is deployed behind a feature flag and is **disabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) can enable it for your instance from the [rails console](../../administration/feature_flags.md#start-the-gitlab-rails-console).
+The busy status feature is deployed behind a feature flag and is **enabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) can disable it for your instance from the [rails console](../../administration/feature_flags.md#start-the-gitlab-rails-console).
+
+To disable it:
+
+```ruby
+Feature.disable(:set_user_availability_status)
+```
To enable it:
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index a8cbc5a5aff..c01efd9c813 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -62,6 +62,7 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/publish` | ✓ | | | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906)) **(ULTIMATE)** |
| `/reassign @user1 @user2` | ✓ | ✓ | | Replace current assignees with those specified. **(STARTER)** |
| `/rebase` | | ✓ | | Rebase source branch. This will schedule a background task that attempt to rebase the changes in the source branch on the latest commit of the target branch. If `/rebase` is used, `/merge` will be ignored to avoid a race condition where the source branch is merged or deleted before it is rebased. |
+| `/reassign_reviewer @user1 @user2` | | ✓ | | Replace current reviewers with those specified. **(STARTER)** |
| `/relabel ~label1 ~label2` | ✓ | ✓ | ✓ | Replace current labels with those specified. |
| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related. **(STARTER)** |
| `/remove_child_epic <epic>` | | | ✓ | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab/-/issues/7330)). **(ULTIMATE)** |
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index 94e71318607..7956cf14203 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -60,7 +60,7 @@ module Gitlab
end
def self.ci_pipeline_editor_page_enabled?(project)
- ::Feature.enabled?(:ci_pipeline_editor_page, project, default_enabled: false)
+ ::Feature.enabled?(:ci_pipeline_editor_page, project, default_enabled: :yaml)
end
def self.allow_failure_with_exit_codes_enabled?
diff --git a/lib/gitlab/ci/status/group/factory.rb b/lib/gitlab/ci/status/group/factory.rb
index ee785856fdd..37e2b7320e2 100644
--- a/lib/gitlab/ci/status/group/factory.rb
+++ b/lib/gitlab/ci/status/group/factory.rb
@@ -8,6 +8,10 @@ module Gitlab
def self.common_helpers
Status::Group::Common
end
+
+ def self.extended_statuses
+ [[Status::SuccessWarning]]
+ end
end
end
end
diff --git a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
index 377c72e8031..7e2828d010f 100644
--- a/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
@@ -17,6 +17,7 @@ variables:
cache:
paths:
- .terraform
+ - .terraform.lock.hcl
before_script:
- alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
diff --git a/lib/gitlab/ci/templates/Terraform/Base.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Terraform/Base.latest.gitlab-ci.yml
index 910e711f046..c2db0fc44f1 100644
--- a/lib/gitlab/ci/templates/Terraform/Base.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Terraform/Base.latest.gitlab-ci.yml
@@ -19,6 +19,7 @@ cache:
key: "${TF_ROOT}"
paths:
- ${TF_ROOT}/.terraform/
+ - ${TF_ROOT}/.terraform.lock.hcl
.init: &init
stage: init
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index f6601379202..e316d52ac05 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -801,7 +801,8 @@ module Gitlab
# forced - should we use --force flag?
# no_tags - should we use --no-tags flag?
# prune - should we use --prune flag?
- def fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, prune: true)
+ # check_tags_changed - should we ask gitaly to calculate whether any tags changed?
+ def fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, prune: true, check_tags_changed: false)
wrapped_gitaly_errors do
gitaly_repository_client.fetch_remote(
remote,
@@ -809,6 +810,7 @@ module Gitlab
forced: forced,
no_tags: no_tags,
prune: prune,
+ check_tags_changed: check_tags_changed,
timeout: GITLAB_PROJECTS_TIMEOUT
)
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index e41a406ebd3..bd450249355 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -70,10 +70,11 @@ module Gitlab
end.join
end
- def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true)
+ def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true, check_tags_changed: false)
request = Gitaly::FetchRemoteRequest.new(
repository: @gitaly_repo, remote: remote, force: forced,
- no_tags: no_tags, timeout: timeout, no_prune: !prune
+ no_tags: no_tags, timeout: timeout, no_prune: !prune,
+ check_tags_changed: check_tags_changed
)
if ssh_auth&.ssh_mirror_url?
diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml
index 2ac408d59e2..4b033d909e0 100644
--- a/lib/gitlab/usage_data_counters/known_events/common.yml
+++ b/lib/gitlab/usage_data_counters/known_events/common.yml
@@ -486,6 +486,16 @@
category: code_review
aggregation: weekly
feature_flag: usage_data_i_code_review_user_remove_mr_comment
+- name: i_code_review_user_create_review_note
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
+ feature_flag: usage_data_i_code_review_user_create_review_note
+- name: i_code_review_user_publish_review
+ redis_slot: code_review
+ category: code_review
+ aggregation: weekly
+ feature_flag: usage_data_i_code_review_user_publish_review
# Terraform
- name: p_terraform_state_api_unique_users
category: terraform
diff --git a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
index 4b98ff50028..6309ca0201a 100644
--- a/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
+++ b/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter.rb
@@ -13,6 +13,8 @@ module Gitlab
MR_CREATE_COMMENT_ACTION = 'i_code_review_user_create_mr_comment'
MR_EDIT_COMMENT_ACTION = 'i_code_review_user_edit_mr_comment'
MR_REMOVE_COMMENT_ACTION = 'i_code_review_user_remove_mr_comment'
+ MR_CREATE_REVIEW_NOTE_ACTION = 'i_code_review_user_create_review_note'
+ MR_PUBLISH_REVIEW_ACTION = 'i_code_review_user_publish_review'
class << self
def track_mr_diffs_action(merge_request:)
@@ -52,6 +54,14 @@ module Gitlab
track_unique_action_by_user(MR_REMOVE_COMMENT_ACTION, user)
end
+ def track_create_review_note_action(user:)
+ track_unique_action_by_user(MR_CREATE_REVIEW_NOTE_ACTION, user)
+ end
+
+ def track_publish_review_action(user:)
+ track_unique_action_by_user(MR_PUBLISH_REVIEW_ACTION, user)
+ end
+
private
def track_unique_action_by_merge_request(action, merge_request)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c5ed8add78a..e8e66359044 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5218,6 +5218,12 @@ msgstr ""
msgid "Change permissions"
msgstr ""
+msgid "Change reviewer(s)"
+msgstr ""
+
+msgid "Change reviewer(s)."
+msgstr ""
+
msgid "Change status"
msgstr ""
@@ -5284,6 +5290,9 @@ msgstr ""
msgid "Changed assignee(s)."
msgstr ""
+msgid "Changed reviewer(s)."
+msgstr ""
+
msgid "Changed the title to \"%{title_param}\"."
msgstr ""
@@ -25263,6 +25272,9 @@ msgstr ""
msgid "See the affected projects in the GitLab admin panel"
msgstr ""
+msgid "See the list of available commands in Slack after setting up this service by entering"
+msgstr ""
+
msgid "See vulnerability %{vulnerability_link} for any Remediation details."
msgstr ""
@@ -29078,6 +29090,9 @@ msgstr ""
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
+msgid "This service allows users to perform common operations on this project by entering slash commands in Slack."
+msgstr ""
+
msgid "This setting can be overridden in each project."
msgstr ""
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index b5f411c9121..d81b067ffb6 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -77,4 +77,34 @@ RSpec.describe Admin::ProjectsController do
expect(response.body).to match(project.name)
end
end
+
+ describe 'PUT /projects/transfer/:id' do
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:new_namespace) { create(:namespace) }
+
+ it 'updates namespace' do
+ put :transfer, params: { namespace_id: project.namespace.path, new_namespace_id: new_namespace.id, id: project.path }
+
+ project.reload
+
+ expect(project.namespace).to eq(new_namespace)
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(response).to redirect_to(admin_project_path(project))
+ end
+
+ context 'when project transfer fails' do
+ it 'flashes error' do
+ old_namespace = project.namespace
+
+ put :transfer, params: { namespace_id: old_namespace.path, new_namespace_id: nil, id: project.path }
+
+ project.reload
+
+ expect(project.namespace).to eq(old_namespace)
+ expect(response).to have_gitlab_http_status(:redirect)
+ expect(response).to redirect_to(admin_project_path(project))
+ expect(flash[:alert]).to eq s_('TransferProject|Please select a new namespace for your project.')
+ end
+ end
+ end
end
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index f811f13def8..2df94a06b3e 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -51,10 +51,27 @@ RSpec.describe Oauth::AuthorizationsController do
end
end
+ shared_examples "Implicit grant can't be used in confidential application" do
+ context 'when application is confidential' do
+ before do
+ application.update(confidential: true)
+ params[:response_type] = 'token'
+ end
+
+ it 'does not allow the implicit flow' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template('doorkeeper/authorizations/error')
+ end
+ end
+ end
+
describe 'GET #new' do
subject { get :new, params: params }
include_examples 'OAuth Authorizations require confirmed user'
+ include_examples "Implicit grant can't be used in confidential application"
context 'when the user is confirmed' do
let(:confirmed_at) { 1.hour.ago }
@@ -95,26 +112,14 @@ RSpec.describe Oauth::AuthorizationsController do
subject { post :create, params: params }
include_examples 'OAuth Authorizations require confirmed user'
-
- context 'when application is confidential' do
- before do
- application.update(confidential: true)
- params[:response_type] = 'token'
- end
-
- it 'does not allow the implicit flow' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template('doorkeeper/authorizations/error')
- end
- end
+ include_examples "Implicit grant can't be used in confidential application"
end
describe 'DELETE #destroy' do
subject { delete :destroy, params: params }
include_examples 'OAuth Authorizations require confirmed user'
+ include_examples "Implicit grant can't be used in confidential application"
end
it 'includes Two-factor enforcement concern' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 8e738af7dd0..20e6021be5c 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -733,7 +733,7 @@ RSpec.describe ProjectsController do
describe '#transfer', :enable_admin_mode do
render_views
- let_it_be(:project, reload: true) { create(:project, :repository) }
+ let_it_be(:project, reload: true) { create(:project) }
let_it_be(:admin) { create(:admin) }
let_it_be(:new_namespace) { create(:namespace) }
diff --git a/spec/frontend/api/api_utils_spec.js b/spec/frontend/api/api_utils_spec.js
new file mode 100644
index 00000000000..3fec26f0149
--- /dev/null
+++ b/spec/frontend/api/api_utils_spec.js
@@ -0,0 +1,35 @@
+import * as apiUtils from '~/api/api_utils';
+
+describe('~/api/api_utils.js', () => {
+ describe('buildApiUrl', () => {
+ beforeEach(() => {
+ window.gon = {
+ api_version: 'v7',
+ };
+ });
+
+ it('returns a URL with the correct API version', () => {
+ expect(apiUtils.buildApiUrl('/api/:version/users/:id/status')).toEqual(
+ '/api/v7/users/:id/status',
+ );
+ });
+
+ it('only replaces the first instance of :version in the URL', () => {
+ expect(apiUtils.buildApiUrl('/api/:version/projects/:id/packages/:version')).toEqual(
+ '/api/v7/projects/:id/packages/:version',
+ );
+ });
+
+ describe('when gon includes a relative_url_root property', () => {
+ beforeEach(() => {
+ window.gon.relative_url_root = '/relative/root';
+ });
+
+ it('returns a URL with the correct relative root URL and API version', () => {
+ expect(apiUtils.buildApiUrl('/api/:version/users/:id/status')).toEqual(
+ '/relative/root/api/v7/users/:id/status',
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/commons/nav/user_merge_requests_spec.js b/spec/frontend/commons/nav/user_merge_requests_spec.js
index 5f81067f84f..c441668f7c7 100644
--- a/spec/frontend/commons/nav/user_merge_requests_spec.js
+++ b/spec/frontend/commons/nav/user_merge_requests_spec.js
@@ -3,7 +3,7 @@ import {
closeUserCountsBroadcast,
refreshUserMergeRequestCounts,
} from '~/commons/nav/user_merge_requests';
-import Api from '~/api';
+import * as UserApi from '~/api/user_api';
jest.mock('~/api');
@@ -33,14 +33,12 @@ describe('User Merge Requests', () => {
describe('refreshUserMergeRequestCounts', () => {
beforeEach(() => {
- Api.userCounts.mockReturnValue(
- Promise.resolve({
- data: {
- assigned_merge_requests: TEST_COUNT,
- review_requested_merge_requests: TEST_COUNT,
- },
- }),
- );
+ jest.spyOn(UserApi, 'getUserCounts').mockResolvedValue({
+ data: {
+ assigned_merge_requests: TEST_COUNT,
+ review_requested_merge_requests: TEST_COUNT,
+ },
+ });
});
describe('with open broadcast channel', () => {
@@ -55,7 +53,7 @@ describe('User Merge Requests', () => {
});
it('calls the API', () => {
- expect(Api.userCounts).toHaveBeenCalled();
+ expect(UserApi.getUserCounts).toHaveBeenCalled();
});
it('posts count to BroadcastChannel', () => {
diff --git a/spec/frontend/invite_members/components/members_token_select_spec.js b/spec/frontend/invite_members/components/members_token_select_spec.js
index d66b715689f..ff123a13ce7 100644
--- a/spec/frontend/invite_members/components/members_token_select_spec.js
+++ b/spec/frontend/invite_members/components/members_token_select_spec.js
@@ -3,7 +3,7 @@ import { nextTick } from 'vue';
import { GlTokenSelector } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
import { stubComponent } from 'helpers/stub_component';
-import Api from '~/api';
+import * as UserApi from '~/api/user_api';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
const label = 'testgroup';
@@ -28,7 +28,7 @@ describe('MembersTokenSelect', () => {
let wrapper;
beforeEach(() => {
- jest.spyOn(Api, 'users').mockResolvedValue({ data: allUsers });
+ jest.spyOn(UserApi, 'getUsers').mockResolvedValue({ data: allUsers });
wrapper = createComponent();
});
@@ -57,7 +57,7 @@ describe('MembersTokenSelect', () => {
await waitForPromises();
- expect(Api.users).not.toHaveBeenCalled();
+ expect(UserApi.getUsers).not.toHaveBeenCalled();
});
});
@@ -90,7 +90,10 @@ describe('MembersTokenSelect', () => {
await waitForPromises();
- expect(Api.users).toHaveBeenCalledWith(searchParam, wrapper.vm.$options.queryOptions);
+ expect(UserApi.getUsers).toHaveBeenCalledWith(
+ searchParam,
+ wrapper.vm.$options.queryOptions,
+ );
expect(tokenSelector.props('hideDropdownWithNoItems')).toBe(false);
});
diff --git a/spec/frontend/lib/utils/users_cache_spec.js b/spec/frontend/lib/utils/users_cache_spec.js
index bc00a5d5409..4034f39ee9c 100644
--- a/spec/frontend/lib/utils/users_cache_spec.js
+++ b/spec/frontend/lib/utils/users_cache_spec.js
@@ -1,4 +1,4 @@
-import Api from '~/api';
+import * as UserApi from '~/api/user_api';
import UsersCache from '~/lib/utils/users_cache';
describe('UsersCache', () => {
@@ -88,7 +88,9 @@ describe('UsersCache', () => {
let apiSpy;
beforeEach(() => {
- jest.spyOn(Api, 'users').mockImplementation((query, options) => apiSpy(query, options));
+ jest
+ .spyOn(UserApi, 'getUsers')
+ .mockImplementation((query, options) => apiSpy(query, options));
});
it('stores and returns data from API call if cache is empty', (done) => {
@@ -151,7 +153,7 @@ describe('UsersCache', () => {
let apiSpy;
beforeEach(() => {
- jest.spyOn(Api, 'user').mockImplementation((id) => apiSpy(id));
+ jest.spyOn(UserApi, 'getUser').mockImplementation((id) => apiSpy(id));
});
it('stores and returns data from API call if cache is empty', (done) => {
@@ -208,7 +210,7 @@ describe('UsersCache', () => {
let apiSpy;
beforeEach(() => {
- jest.spyOn(Api, 'userStatus').mockImplementation((id) => apiSpy(id));
+ jest.spyOn(UserApi, 'getUserStatus').mockImplementation((id) => apiSpy(id));
});
it('stores and returns data from API call if cache is empty', (done) => {
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 912778a1546..1bc74609b6f 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -918,7 +918,6 @@ describe('Actions Notes Store', () => {
testSubmitSuggestion(done, () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_RESOLVING_DISCUSSION, true],
- [mutationTypes.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }],
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
@@ -1001,8 +1000,6 @@ describe('Actions Notes Store', () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
[mutationTypes.SET_RESOLVING_DISCUSSION, true],
- [mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[0]],
- [mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[1]],
[mutationTypes.CLEAR_SUGGESTION_BATCH],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
@@ -1066,8 +1063,6 @@ describe('Actions Notes Store', () => {
expect(commit.mock.calls).toEqual([
[mutationTypes.SET_APPLYING_BATCH_STATE, true],
[mutationTypes.SET_RESOLVING_DISCUSSION, true],
- [mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[0]],
- [mutationTypes.APPLY_SUGGESTION, batchSuggestionsInfo[1]],
[mutationTypes.CLEAR_SUGGESTION_BATCH],
[mutationTypes.SET_APPLYING_BATCH_STATE, false],
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
diff --git a/spec/frontend/performance_bar/index_spec.js b/spec/frontend/performance_bar/index_spec.js
index bcd2cbbd530..8d9c32b7f12 100644
--- a/spec/frontend/performance_bar/index_spec.js
+++ b/spec/frontend/performance_bar/index_spec.js
@@ -4,25 +4,23 @@ import '~/performance_bar/components/performance_bar_app.vue';
import performanceBar from '~/performance_bar';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
+jest.mock('~/performance_bar/performance_bar_log');
+
describe('performance bar wrapper', () => {
let mock;
let vm;
beforeEach(() => {
+ setFixtures('<div id="js-peek"></div>');
+ const peekWrapper = document.getElementById('js-peek');
performance.getEntriesByType = jest.fn().mockReturnValue([]);
- // clear html so that elements from previous tests don't mess with this test
- document.body.innerHTML = '';
- const peekWrapper = document.createElement('div');
-
peekWrapper.setAttribute('id', 'js-peek');
peekWrapper.setAttribute('data-env', 'development');
peekWrapper.setAttribute('data-request-id', '123');
peekWrapper.setAttribute('data-peek-url', '/-/peek/results');
peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true');
- document.body.appendChild(peekWrapper);
-
mock = new MockAdapter(axios);
mock.onGet('/-/peek/results').reply(
@@ -48,6 +46,7 @@ describe('performance bar wrapper', () => {
afterEach(() => {
vm.$destroy();
+ document.getElementById('js-peek').remove();
mock.restore();
});
diff --git a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
index 273dfeeb6f3..f3085fb7ffb 100644
--- a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
+++ b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
@@ -1,13 +1,12 @@
import { shallowMount } from '@vue/test-utils';
import { GlModal, GlFormCheckbox } from '@gitlab/ui';
import { initEmojiMock } from 'helpers/emoji';
-import Api from '~/api';
+import * as UserApi from '~/api/user_api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import SetStatusModalWrapper, {
AVAILABILITY_STATUS,
} from '~/set_status_modal/set_status_modal_wrapper.vue';
-jest.mock('~/api');
jest.mock('~/flash');
describe('SetStatusModalWrapper', () => {
@@ -150,7 +149,7 @@ describe('SetStatusModalWrapper', () => {
describe('update status', () => {
describe('succeeds', () => {
beforeEach(() => {
- jest.spyOn(Api, 'postUserStatus').mockResolvedValue();
+ jest.spyOn(UserApi, 'updateUserStatus').mockResolvedValue();
});
it('clicking "removeStatus" clears the emoji and message fields', async () => {
@@ -173,12 +172,12 @@ describe('SetStatusModalWrapper', () => {
const commonParams = { emoji: defaultEmoji, message: defaultMessage };
- expect(Api.postUserStatus).toHaveBeenCalledTimes(2);
- expect(Api.postUserStatus).toHaveBeenNthCalledWith(1, {
+ expect(UserApi.updateUserStatus).toHaveBeenCalledTimes(2);
+ expect(UserApi.updateUserStatus).toHaveBeenNthCalledWith(1, {
availability: AVAILABILITY_STATUS.NOT_SET,
...commonParams,
});
- expect(Api.postUserStatus).toHaveBeenNthCalledWith(2, {
+ expect(UserApi.updateUserStatus).toHaveBeenNthCalledWith(2, {
availability: AVAILABILITY_STATUS.BUSY,
...commonParams,
});
@@ -196,7 +195,7 @@ describe('SetStatusModalWrapper', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
- jest.spyOn(Api, 'postUserStatus').mockResolvedValue();
+ jest.spyOn(UserApi, 'updateUserStatus').mockResolvedValue();
return initModal({ mockOnUpdateSuccess: false });
});
@@ -210,7 +209,7 @@ describe('SetStatusModalWrapper', () => {
describe('with errors', () => {
beforeEach(() => {
- jest.spyOn(Api, 'postUserStatus').mockRejectedValue();
+ jest.spyOn(UserApi, 'updateUserStatus').mockRejectedValue();
});
it('calls the "onUpdateFail" handler', async () => {
@@ -225,7 +224,7 @@ describe('SetStatusModalWrapper', () => {
beforeEach(async () => {
mockEmoji = await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
- jest.spyOn(Api, 'postUserStatus').mockRejectedValue();
+ jest.spyOn(UserApi, 'updateUserStatus').mockRejectedValue();
return initModal({ mockOnUpdateFailure: false });
});
diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb
index 6267b26aa78..c67c7ff8271 100644
--- a/spec/lib/gitlab/ci/status/group/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb
@@ -12,4 +12,9 @@ RSpec.describe Gitlab::Ci::Status::Group::Factory do
expect(described_class.common_helpers)
.to eq Gitlab::Ci::Status::Group::Common
end
+
+ it 'exposes extended statuses' do
+ expect(described_class.extended_statuses)
+ .to eq([[Gitlab::Ci::Status::SuccessWarning]])
+ end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index c28d4672a80..ef9b5a30c86 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -520,12 +520,13 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
forced: true,
no_tags: true,
timeout: described_class::GITLAB_PROJECTS_TIMEOUT,
- prune: false
+ prune: false,
+ check_tags_changed: false
}
expect(repository.gitaly_repository_client).to receive(:fetch_remote).with('remote-name', expected_opts)
- repository.fetch_remote('remote-name', ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false)
+ repository.fetch_remote('remote-name', ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false, check_tags_changed: false)
end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :fetch_remote do
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index f810a5c15a5..7a382df1248 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -131,7 +131,8 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
known_hosts: '',
force: false,
no_tags: false,
- no_prune: false
+ no_prune: false,
+ check_tags_changed: false
)
expect_any_instance_of(Gitaly::RepositoryService::Stub)
@@ -139,7 +140,7 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
.with(expected_request, kind_of(Hash))
.and_return(double(value: true))
- client.fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, timeout: 1)
+ client.fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false, timeout: 1, check_tags_changed: false)
end
context 'SSH auth' do
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
index d920f260cce..0dec9dbfeea 100644
--- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb
@@ -95,4 +95,20 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl
let(:action) { described_class::MR_REMOVE_COMMENT_ACTION }
end
end
+
+ describe '.track_create_review_note_action' do
+ subject { described_class.track_create_review_note_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_CREATE_REVIEW_NOTE_ACTION }
+ end
+ end
+
+ describe '.track_publish_review_action' do
+ subject { described_class.track_publish_review_action(user: user) }
+
+ it_behaves_like 'a tracked merge request unique event' do
+ let(:action) { described_class::MR_PUBLISH_REVIEW_ACTION }
+ end
+ end
end
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index c20b7e61044..6c96e659a34 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -54,6 +54,18 @@ RSpec.describe Ci::Group do
.to be_a(Gitlab::Ci::Status::Failed)
end
end
+
+ context 'when one of the commit statuses in the group is allowed to fail' do
+ let(:jobs) do
+ [create(:ci_build, :failed, :allowed_to_fail),
+ create(:ci_build, :success)]
+ end
+
+ it 'fabricates a new detailed status object' do
+ expect(subject.detailed_status(double(:user)))
+ .to be_a(Gitlab::Ci::Status::SuccessWarning)
+ end
+ end
end
describe '.fabricate' do
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index e7cd3d7f537..cd0873bddd2 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -5,12 +5,21 @@ require 'spec_helper'
RSpec.describe JiraService do
include AssetsHelpers
+ let_it_be(:project) { create(:project, :repository) }
let(:url) { 'http://jira.example.com' }
let(:api_url) { 'http://api-jira.example.com' }
let(:username) { 'jira-username' }
let(:password) { 'jira-password' }
let(:transition_id) { 'test27' }
let(:server_info_results) { { 'deploymentType' => 'Cloud' } }
+ let(:jira_service) do
+ described_class.new(
+ project: project,
+ url: url,
+ username: username,
+ password: password
+ )
+ end
before do
WebMock.stub_request(:get, /serverInfo/).to_return(body: server_info_results.to_json )
@@ -19,7 +28,7 @@ RSpec.describe JiraService do
describe '#options' do
let(:options) do
{
- project: create(:project),
+ project: project,
active: true,
username: 'username',
password: 'test',
@@ -108,7 +117,7 @@ RSpec.describe JiraService do
describe '#create' do
let(:params) do
{
- project: create(:project),
+ project: project,
url: url, api_url: api_url,
username: username, password: password,
jira_issue_transition_id: transition_id
@@ -434,10 +443,23 @@ RSpec.describe JiraService do
end
end
+ describe '#find_issue' do
+ let(:issue_key) { 'JIRA-123' }
+ let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}" }
+
+ before do
+ stub_request(:get, issue_url).with(basic_auth: [username, password])
+ end
+
+ it 'call the Jira API to get the issue' do
+ jira_service.find_issue(issue_key)
+
+ expect(WebMock).to have_requested(:get, issue_url)
+ end
+ end
+
describe '#close_issue' do
let(:custom_base_url) { 'http://custom_url' }
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
shared_examples 'close_issue' do
before do
@@ -445,7 +467,6 @@ RSpec.describe JiraService do
allow(@jira_service).to receive_messages(
project_id: project.id,
project: project,
- service_hook: true,
url: 'http://jira.example.com',
username: 'gitlab_jira_username',
password: 'gitlab_jira_password',
@@ -657,17 +678,7 @@ RSpec.describe JiraService do
end
describe '#create_cross_reference_note' do
- let_it_be(:user) { build_stubbed(:user) }
- let_it_be(:project) { create(:project, :repository) }
- let(:jira_service) do
- described_class.new(
- project: project,
- url: url,
- username: username,
- password: password
- )
- end
-
+ let_it_be(:user) { build_stubbed(:user) }
let(:jira_issue) { ExternalIssue.new('JIRA-123', project) }
subject { jira_service.create_cross_reference_note(jira_issue, resource, user) }
@@ -732,15 +743,6 @@ RSpec.describe JiraService do
describe '#test' do
let(:server_info_results) { { 'url' => 'http://url', 'deploymentType' => 'Cloud' } }
- let_it_be(:project) { create(:project, :repository) }
- let(:jira_service) do
- described_class.new(
- url: url,
- project: project,
- username: username,
- password: password
- )
- end
def server_info
jira_service.test(nil)
@@ -790,7 +792,6 @@ RSpec.describe JiraService do
}
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
- project = create(:project)
service = project.create_jira_service(active: true)
expect(service.url).to eq('http://jira.sample/projects/project_a')
diff --git a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
index b3db8b56b7c..1edcef2977b 100644
--- a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
+++ b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared
describe '.execute' do
subject { service.execute }
- let_it_be(:artifact, reload: true) do
+ let_it_be(:artifact, refind: true) do
create(:ci_job_artifact, expire_at: 1.day.ago)
end
@@ -164,13 +164,21 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared
end
context 'when timeout happens' do
+ let!(:second_artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) }
+
before do
- stub_const('Ci::DestroyExpiredJobArtifactsService::LOOP_TIMEOUT', 1.second)
- allow_any_instance_of(described_class).to receive(:destroy_pipeline_artifacts_batch) { true }
+ stub_const('Ci::DestroyExpiredJobArtifactsService::LOOP_TIMEOUT', 0.seconds)
+ stub_const('Ci::DestroyExpiredJobArtifactsService::BATCH_SIZE', 1)
+
+ second_artifact.job.pipeline.unlocked!
end
- it 'returns false and does not continue destroying' do
- is_expected.to be_falsy
+ it 'destroys one artifact' do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(-1)
+ end
+
+ it 'reports the number of destroyed artifacts' do
+ is_expected.to eq(1)
end
end
@@ -187,6 +195,10 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared
it 'destroys one artifact' do
expect { subject }.to change { Ci::JobArtifact.count }.by(-1)
end
+
+ it 'reports the number of destroyed artifacts' do
+ is_expected.to eq(1)
+ end
end
context 'when there are no artifacts' do
@@ -197,6 +209,10 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared
it 'does not raise error' do
expect { subject }.not_to raise_error
end
+
+ it 'reports the number of destroyed artifacts' do
+ is_expected.to eq(0)
+ end
end
context 'when there are artifacts more than batch sizes' do
@@ -211,45 +227,9 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared
it 'destroys all expired artifacts' do
expect { subject }.to change { Ci::JobArtifact.count }.by(-2)
end
- end
-
- context 'when artifact is a pipeline artifact' do
- context 'when artifacts are expired' do
- let!(:pipeline_artifact_1) { create(:ci_pipeline_artifact, expire_at: 1.week.ago) }
- let!(:pipeline_artifact_2) { create(:ci_pipeline_artifact, expire_at: 1.week.ago) }
- before do
- [pipeline_artifact_1, pipeline_artifact_2].each { |pipeline_artifact| pipeline_artifact.pipeline.unlocked! }
-
- stub_feature_flags(ci_split_pipeline_artifacts_removal: false)
- end
-
- it 'destroys pipeline artifacts' do
- expect { subject }.to change { Ci::PipelineArtifact.count }.by(-2)
- end
-
- context 'with ci_split_pipeline_artifacts_removal enabled' do
- before do
- stub_feature_flags(ci_split_pipeline_artifacts_removal: true)
- end
-
- it 'does not destroy pipeline artifacts' do
- expect { subject }.not_to change { Ci::PipelineArtifact.count }
- end
- end
- end
-
- context 'when artifacts are not expired' do
- let!(:pipeline_artifact_1) { create(:ci_pipeline_artifact, expire_at: 2.days.from_now) }
- let!(:pipeline_artifact_2) { create(:ci_pipeline_artifact, expire_at: 2.days.from_now) }
-
- before do
- [pipeline_artifact_1, pipeline_artifact_2].each { |pipeline_artifact| pipeline_artifact.pipeline.unlocked! }
- end
-
- it 'does not destroy pipeline artifacts' do
- expect { subject }.not_to change { Ci::PipelineArtifact.count }
- end
+ it 'reports the number of destroyed artifacts' do
+ is_expected.to eq(2)
end
end
@@ -265,16 +245,4 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared
end
end
end
-
- describe '.destroy_job_artifacts_batch' do
- it 'returns a falsy value without artifacts' do
- expect(service.send(:destroy_job_artifacts_batch)).to be_falsy
- end
- end
-
- describe '.destroy_pipeline_artifacts_batch' do
- it 'returns a falsy value without artifacts' do
- expect(service.send(:destroy_pipeline_artifacts_batch)).to be_falsy
- end
- end
end
diff --git a/spec/services/draft_notes/create_service_spec.rb b/spec/services/draft_notes/create_service_spec.rb
index f0291067777..9e084dbed1c 100644
--- a/spec/services/draft_notes/create_service_spec.rb
+++ b/spec/services/draft_notes/create_service_spec.rb
@@ -20,6 +20,23 @@ RSpec.describe DraftNotes::CreateService do
expect(draft.discussion_id).to be_nil
end
+ it 'tracks the start event when the draft is persisted' do
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .to receive(:track_create_review_note_action)
+ .with(user: user)
+
+ draft = create_draft(note: 'This is a test')
+ expect(draft).to be_persisted
+ end
+
+ it 'does not track the start event when the draft is not persisted' do
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .not_to receive(:track_create_review_note_action)
+
+ draft = create_draft(note: 'Not a reply!', resolve_discussion: true)
+ expect(draft).not_to be_persisted
+ end
+
it 'cannot resolve when there is nothing to resolve' do
draft = create_draft(note: 'Not a reply!', resolve_discussion: true)
diff --git a/spec/services/draft_notes/publish_service_spec.rb b/spec/services/draft_notes/publish_service_spec.rb
index ae0c8113904..f83e91b683f 100644
--- a/spec/services/draft_notes/publish_service_spec.rb
+++ b/spec/services/draft_notes/publish_service_spec.rb
@@ -43,6 +43,13 @@ RSpec.describe DraftNotes::PublishService do
expect(result[:status]).to eq(:success)
end
+ it 'does not track the publish event' do
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .not_to receive(:track_publish_review_action)
+
+ publish(draft: drafts.first)
+ end
+
context 'commit_id is set' do
let(:commit_id) { commit.id }
@@ -74,6 +81,13 @@ RSpec.describe DraftNotes::PublishService do
expect { publish }.not_to change { DraftNote.count }
end
+ it 'does not track the publish event' do
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .not_to receive(:track_publish_review_action)
+
+ publish
+ end
+
it 'returns an error' do
result = publish
@@ -105,6 +119,14 @@ RSpec.describe DraftNotes::PublishService do
publish
end
+ it 'tracks the publish event' do
+ expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
+ .to receive(:track_publish_review_action)
+ .with(user: user)
+
+ publish
+ end
+
context 'commit_id is set' do
let(:commit_id) { commit.id }
diff --git a/spec/support/shared_examples/models/boards/listable_shared_examples.rb b/spec/support/shared_examples/models/boards/listable_shared_examples.rb
index 49b00076a46..e733a5488fb 100644
--- a/spec/support/shared_examples/models/boards/listable_shared_examples.rb
+++ b/spec/support/shared_examples/models/boards/listable_shared_examples.rb
@@ -68,6 +68,12 @@ RSpec.shared_examples 'boards listable model' do |list_factory|
expect(subject.title).to eq 'Development'
end
+ it 'returns Open when list_type is set to backlog' do
+ subject.list_type = :backlog
+
+ expect(subject.title).to eq 'Open'
+ end
+
it 'returns Closed when list_type is set to closed' do
subject.list_type = :closed
diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb
index 995f37daf17..6d73d715d21 100644
--- a/spec/workers/expire_build_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_artifacts_worker_spec.rb
@@ -8,9 +8,11 @@ RSpec.describe ExpireBuildArtifactsWorker do
describe '#perform' do
it 'executes a service' do
expect_next_instance_of(Ci::DestroyExpiredJobArtifactsService) do |instance|
- expect(instance).to receive(:execute)
+ expect(instance).to receive(:execute).and_call_original
end
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:destroyed_job_artifacts_count, 0)
+
worker.perform
end
end