summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-09 15:08:47 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-09 15:08:47 +0000
commit825e4190a30a745adb37ab822eb8f9d845b462e7 (patch)
tree2588f929e0dc2bd97bf9b57eee596ae65315144c
parentd022b7432efd720f0cf5f8d2a2cdaac7619bab57 (diff)
downloadgitlab-ce-825e4190a30a745adb37ab822eb8f9d845b462e7.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue3
-rw-r--r--app/assets/javascripts/packages/details/components/app.vue27
-rw-r--r--app/assets/javascripts/packages/details/store/actions.js13
-rw-r--r--app/assets/javascripts/packages/details/store/index.js6
-rw-r--r--app/assets/javascripts/packages/list/components/packages_list_app.vue16
-rw-r--r--app/assets/javascripts/packages/list/constants.js1
-rw-r--r--app/assets/javascripts/packages/list/stores/actions.js2
-rw-r--r--app/assets/javascripts/packages/shared/constants.js5
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue2
-rw-r--r--app/assets/javascripts/static_site_editor/services/formatter.js44
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js2
-rw-r--r--app/services/concerns/measurable.rb2
-rw-r--r--app/views/projects/merge_requests/diffs/_different_base.html.haml2
-rw-r--r--app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml2
-rw-r--r--app/views/projects/packages/packages/show.html.haml5
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/milestones/_description.html.haml3
-rw-r--r--changelogs/unreleased/225948-replace-fa-info-circle-icons-with-gitlab-svg-information-icon.yml5
-rw-r--r--changelogs/unreleased/243758-indent-two-spaced-lists.yml5
-rw-r--r--changelogs/unreleased/app-logger-6.yml5
-rw-r--r--changelogs/unreleased/app-logger-7.yml5
-rw-r--r--changelogs/unreleased/app-logger-9.yml5
-rw-r--r--changelogs/unreleased/ph-225410-commentHasWrongAuthorName.yml5
-rw-r--r--changelogs/unreleased/psi-milestone-header-fix.yml5
-rw-r--r--doc/development/feature_flags/development.md51
-rw-r--r--doc/user/project/repository/repository_mirroring.md3
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/feature/shared.rb14
-rw-r--r--lib/gitlab/metrics/methods.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb2
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/page/project/pipeline/show.rb8
-rw-r--r--qa/qa/page/sub_menus/common.rb2
-rw-r--r--spec/frontend/packages/details/components/app_spec.js120
-rw-r--r--spec/frontend/packages/details/store/actions_spec.js24
-rw-r--r--spec/frontend/packages/list/components/packages_list_app_spec.js49
-rw-r--r--spec/frontend/packages/list/stores/actions_spec.js3
-rw-r--r--spec/frontend/static_site_editor/services/formatter_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js4
-rw-r--r--spec/lib/api/helpers_spec.rb2
41 files changed, 399 insertions, 79 deletions
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index e5f1fbff687..180d12c9e21 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -361,7 +361,7 @@ export default {
</template>
</gl-sprintf>
</div>
- <div v-once class="timeline-icon">
+ <div class="timeline-icon">
<user-avatar-link
:link-href="author.path"
:img-src="author.avatar_url"
@@ -374,7 +374,6 @@ export default {
<div class="timeline-content">
<div class="note-header">
<note-header
- v-once
:author="author"
:created-at="note.created_at"
:note-id="note.id"
diff --git a/app/assets/javascripts/packages/details/components/app.vue b/app/assets/javascripts/packages/details/components/app.vue
index cd620a3a9ac..af3220840a6 100644
--- a/app/assets/javascripts/packages/details/components/app.vue
+++ b/app/assets/javascripts/packages/details/components/app.vue
@@ -25,8 +25,9 @@ import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { __, s__ } from '~/locale';
-import { PackageType, TrackingActions } from '../../shared/constants';
+import { PackageType, TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '../../shared/constants';
import { packageTypeToTrackCategory } from '../../shared/utils';
+import { objectToQueryString } from '~/lib/utils/common_utils';
export default {
name: 'PackagesApp',
@@ -62,17 +63,15 @@ export default {
'packageFiles',
'isLoading',
'canDelete',
- 'destroyPath',
'svgPath',
'npmPath',
'npmHelpPath',
+ 'projectListUrl',
+ 'groupListUrl',
]),
isValidPackage() {
return Boolean(this.packageEntity.name);
},
- canDeletePackage() {
- return this.canDelete && this.destroyPath;
- },
filesTableRows() {
return this.packageFiles.map(x => ({
name: x.file_name,
@@ -100,7 +99,7 @@ export default {
},
},
methods: {
- ...mapActions(['fetchPackageVersions']),
+ ...mapActions(['deletePackage', 'fetchPackageVersions']),
formatSize(size) {
return numberToHumanSize(size);
},
@@ -112,6 +111,16 @@ export default {
this.fetchPackageVersions();
}
},
+ async confirmPackageDeletion() {
+ this.track(TrackingActions.DELETE_PACKAGE);
+ await this.deletePackage();
+ const returnTo =
+ !this.groupListUrl || document.referrer.includes(this.projectName)
+ ? this.projectListUrl
+ : this.groupListUrl; // to avoid security issue url are supplied from backend
+ const modalQuery = objectToQueryString({ [SHOW_DELETE_SUCCESS_ALERT]: true });
+ window.location.replace(`${returnTo}?${modalQuery}`);
+ },
},
i18n: {
deleteModalTitle: s__(`PackageRegistry|Delete Package Version`),
@@ -150,7 +159,7 @@ export default {
<package-title>
<template #delete-button>
<gl-button
- v-if="canDeletePackage"
+ v-if="canDelete"
v-gl-modal="'delete-modal'"
class="js-delete-button"
variant="danger"
@@ -272,12 +281,10 @@ export default {
<gl-button @click="cancelDelete">{{ __('Cancel') }}</gl-button>
<gl-button
ref="modal-delete-button"
- data-method="delete"
- :to="destroyPath"
variant="danger"
category="primary"
data-qa-selector="delete_modal_button"
- @click="track($options.trackingActions.DELETE_PACKAGE)"
+ @click="confirmPackageDeletion"
>
{{ __('Delete') }}
</gl-button>
diff --git a/app/assets/javascripts/packages/details/store/actions.js b/app/assets/javascripts/packages/details/store/actions.js
index cda80056e19..340f60258a0 100644
--- a/app/assets/javascripts/packages/details/store/actions.js
+++ b/app/assets/javascripts/packages/details/store/actions.js
@@ -1,9 +1,10 @@
import Api from '~/api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { FETCH_PACKAGE_VERSIONS_ERROR } from '../constants';
+import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/shared/constants';
import * as types from './mutation_types';
-export default ({ commit, state }) => {
+export const fetchPackageVersions = ({ commit, state }) => {
commit(types.SET_LOADING, true);
const { project_id, id } = state.packageEntity;
@@ -21,3 +22,13 @@ export default ({ commit, state }) => {
commit(types.SET_LOADING, false);
});
};
+
+export const deletePackage = ({
+ state: {
+ packageEntity: { project_id, id },
+ },
+}) => {
+ return Api.deleteProjectPackage(project_id, id).catch(() => {
+ createFlash(DELETE_PACKAGE_ERROR_MESSAGE);
+ });
+};
diff --git a/app/assets/javascripts/packages/details/store/index.js b/app/assets/javascripts/packages/details/store/index.js
index 9687eb98544..15e17bcfaac 100644
--- a/app/assets/javascripts/packages/details/store/index.js
+++ b/app/assets/javascripts/packages/details/store/index.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import Vuex from 'vuex';
-import fetchPackageVersions from './actions';
+import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
@@ -8,9 +8,7 @@ Vue.use(Vuex);
export default (initialState = {}) =>
new Vuex.Store({
- actions: {
- fetchPackageVersions,
- },
+ actions,
getters,
mutations,
state: {
diff --git a/app/assets/javascripts/packages/list/components/packages_list_app.vue b/app/assets/javascripts/packages/list/components/packages_list_app.vue
index 9c7725477eb..6304f723f6a 100644
--- a/app/assets/javascripts/packages/list/components/packages_list_app.vue
+++ b/app/assets/javascripts/packages/list/components/packages_list_app.vue
@@ -2,11 +2,14 @@
import { mapActions, mapState } from 'vuex';
import { GlEmptyState, GlTab, GlTabs, GlLink, GlSprintf } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
+import createFlash from '~/flash';
import PackageFilter from './packages_filter.vue';
import PackageList from './packages_list.vue';
import PackageSort from './packages_sort.vue';
-import { PACKAGE_REGISTRY_TABS } from '../constants';
+import { PACKAGE_REGISTRY_TABS, DELETE_PACKAGE_SUCCESS_MESSAGE } from '../constants';
import PackagesComingSoon from '../coming_soon/packages_coming_soon.vue';
+import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
+import { historyReplaceState } from '~/lib/utils/common_utils';
export default {
components: {
@@ -34,6 +37,7 @@ export default {
},
mounted() {
this.requestPackagesList();
+ this.checkDeleteAlert();
},
methods: {
...mapActions(['requestPackagesList', 'requestDeletePackage', 'setSelectedType']),
@@ -64,6 +68,16 @@ export default {
return s__('PackageRegistry|There are no packages yet');
},
+ checkDeleteAlert() {
+ const urlParams = new URLSearchParams(window.location.search);
+ const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT);
+ if (showAlert) {
+ // to be refactored to use gl-alert
+ createFlash({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, type: 'notice' });
+ const cleanUrl = window.location.href.split('?')[0];
+ historyReplaceState(cleanUrl);
+ }
+ },
},
i18n: {
widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'),
diff --git a/app/assets/javascripts/packages/list/constants.js b/app/assets/javascripts/packages/list/constants.js
index 510d04965cb..0ff8c86362d 100644
--- a/app/assets/javascripts/packages/list/constants.js
+++ b/app/assets/javascripts/packages/list/constants.js
@@ -5,7 +5,6 @@ export const FETCH_PACKAGES_LIST_ERROR_MESSAGE = __(
'Something went wrong while fetching the packages list.',
);
export const FETCH_PACKAGE_ERROR_MESSAGE = __('Something went wrong while fetching the package.');
-export const DELETE_PACKAGE_ERROR_MESSAGE = __('Something went wrong while deleting the package.');
export const DELETE_PACKAGE_SUCCESS_MESSAGE = __('Package deleted successfully');
export const DEFAULT_PAGE = 1;
diff --git a/app/assets/javascripts/packages/list/stores/actions.js b/app/assets/javascripts/packages/list/stores/actions.js
index 0ed24aee2c5..bbc11e3cf13 100644
--- a/app/assets/javascripts/packages/list/stores/actions.js
+++ b/app/assets/javascripts/packages/list/stores/actions.js
@@ -1,10 +1,10 @@
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
+import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/shared/constants';
import * as types from './mutation_types';
import {
FETCH_PACKAGES_LIST_ERROR_MESSAGE,
- DELETE_PACKAGE_ERROR_MESSAGE,
DELETE_PACKAGE_SUCCESS_MESSAGE,
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
diff --git a/app/assets/javascripts/packages/shared/constants.js b/app/assets/javascripts/packages/shared/constants.js
index 279c2959fa9..e5131db59bf 100644
--- a/app/assets/javascripts/packages/shared/constants.js
+++ b/app/assets/javascripts/packages/shared/constants.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export const PackageType = {
CONAN: 'conan',
MAVEN: 'maven',
@@ -22,3 +24,6 @@ export const TrackingCategories = {
[PackageType.NPM]: 'NpmPackages',
[PackageType.CONAN]: 'ConanPackages',
};
+
+export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert';
+export const DELETE_PACKAGE_ERROR_MESSAGE = __('Something went wrong while deleting the package.');
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
index 24c9d264239..2831c0d540d 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue
@@ -150,7 +150,7 @@ export default {
:class="`js-pipeline-expand-${pipeline.id} ${expandButtonPosition}`"
:icon="expandedIcon"
data-testid="expandPipelineButton"
- data-qa-selector="linked_pipeline_button"
+ data-qa-selector="expand_pipeline_button"
@click="onClickLinkedPipeline"
/>
</div>
diff --git a/app/assets/javascripts/static_site_editor/services/formatter.js b/app/assets/javascripts/static_site_editor/services/formatter.js
index 92d5e8a5df8..9a5dcd307eb 100644
--- a/app/assets/javascripts/static_site_editor/services/formatter.js
+++ b/app/assets/javascripts/static_site_editor/services/formatter.js
@@ -1,3 +1,45 @@
+import { repeat } from 'lodash';
+
+const topLevelOrderedRegexp = /^\d{1,3}/;
+const nestedLineRegexp = /^\s+/;
+
+/**
+ * DISCLAIMER: This is a temporary fix that corrects the indentation
+ * spaces of list items. This workaround originates in the usage of
+ * the Static Site Editor to edit the Handbook. The Handbook uses a
+ * Markdown parser called Kramdown interprets lines indented
+ * with two spaces as content within a list. For example:
+ *
+ * 1. ordered list
+ * - nested unordered list
+ *
+ * The Static Site Editor uses a different Markdown parser based on the
+ * CommonMark specification (official Markdown spec) called ToastMark.
+ * When the SSE encounters a nested list with only two spaces, it flattens
+ * the list:
+ *
+ * 1. ordered list
+ * - nested unordered list
+ *
+ * This function attempts to correct this problem before the content is loaded
+ * by Toast UI.
+ */
+const correctNestedContentIndenation = source => {
+ const lines = source.split('\n');
+ let topLevelOrderedListDetected = false;
+
+ return lines
+ .reduce((result, line) => {
+ if (topLevelOrderedListDetected && nestedLineRegexp.test(line)) {
+ return [...result, line.replace(nestedLineRegexp, repeat(' ', 4))];
+ }
+
+ topLevelOrderedListDetected = topLevelOrderedRegexp.test(line);
+ return [...result, line];
+ }, [])
+ .join('\n');
+};
+
const removeOrphanedBrTags = source => {
/* Until the underlying Squire editor of Toast UI Editor resolves duplicate `<br>` tags, this
`replace` solution will clear out orphaned `<br>` tags that it generates. Additionally,
@@ -8,7 +50,7 @@ const removeOrphanedBrTags = source => {
};
const format = source => {
- return removeOrphanedBrTags(source);
+ return correctNestedContentIndenation(removeOrphanedBrTags(source));
};
export default format;
diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
index a214151bae9..2bce691e793 100644
--- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
+++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js
@@ -96,7 +96,7 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) =>
const isReferenceDefinition = Boolean(node.dataset.sseReferenceDefinition);
return isReferenceDefinition
- ? `\n\n${node.innerText}\n`
+ ? `\n\n${node.innerText}\n\n`
: baseRenderer.convert(node, subContent);
},
};
diff --git a/app/services/concerns/measurable.rb b/app/services/concerns/measurable.rb
index b099a58a9ae..fcb3022a1dc 100644
--- a/app/services/concerns/measurable.rb
+++ b/app/services/concerns/measurable.rb
@@ -45,7 +45,7 @@ module Measurable
private
def measuring?
- Feature.enabled?("gitlab_service_measuring_#{service_class}")
+ Feature.enabled?("gitlab_service_measuring_#{service_class}", type: :ops)
end
# These attributes are always present in log.
diff --git a/app/views/projects/merge_requests/diffs/_different_base.html.haml b/app/views/projects/merge_requests/diffs/_different_base.html.haml
index 0e57066f9c9..06a15b96653 100644
--- a/app/views/projects/merge_requests/diffs/_different_base.html.haml
+++ b/app/views/projects/merge_requests/diffs/_different_base.html.haml
@@ -1,7 +1,7 @@
- if @merge_request_diff && different_base?(@start_version, @merge_request_diff)
.mr-version-controls
.content-block
- = icon('info-circle')
+ = sprite_icon('information-o')
Selected versions have different base commits.
Changes will include
= link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
diff --git a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
index 8d7138747fb..b9dc37c9b54 100644
--- a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
+++ b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml
@@ -1,7 +1,7 @@
- if @commit || @start_version || (@merge_request_diff && !@merge_request_diff.latest?)
.mr-version-controls
.content-block.comments-disabled-notif.clearfix
- = icon('info-circle')
+ = sprite_icon('information-o')
= succeed '.' do
- if @commit
Only comments from the following commit are shown below
diff --git a/app/views/projects/packages/packages/show.html.haml b/app/views/projects/packages/packages/show.html.haml
index 3373848d3e2..9e68dbc4de1 100644
--- a/app/views/projects/packages/packages/show.html.haml
+++ b/app/views/projects/packages/packages/show.html.haml
@@ -8,7 +8,6 @@
.col-12
#js-vue-packages-detail{ data: { package: package_from_presenter(@package),
can_delete: can?(current_user, :destroy_package, @project).to_s,
- destroy_path: project_package_path(@project, @package),
svg_path: image_path('illustrations/no-packages.svg'),
npm_path: package_registry_instance_url(:npm),
npm_help_path: help_page_path('user/packages/npm_registry/index'),
@@ -23,4 +22,6 @@
pypi_help_path: help_page_path('user/packages/pypi_repository/index'),
composer_path: composer_registry_url(@project&.group&.id),
composer_help_path: help_page_path('user/packages/composer_repository/index'),
- project_name: @project.name} }
+ project_name: @project.name,
+ project_list_url: project_packages_path(@project),
+ group_list_url: @project.group ? group_packages_path(@project.group) : ''} }
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index ac8bed74611..c38fb4efaa0 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -46,7 +46,7 @@
- if @merge_request_to_resolve_discussions_of
.form-group.row
.col-sm-10.offset-sm-2
- = icon('info-circle')
+ = sprite_icon('information-o')
- if @merge_request_to_resolve_discussions_of.discussions_can_be_resolved_by?(current_user)
= hidden_field_tag 'merge_request_to_resolve_discussions_of', @merge_request_to_resolve_discussions_of.iid
- if @discussion_to_resolve
diff --git a/app/views/shared/milestones/_description.html.haml b/app/views/shared/milestones/_description.html.haml
index 76d6c765ed6..679931c5fd9 100644
--- a/app/views/shared/milestones/_description.html.haml
+++ b/app/views/shared/milestones/_description.html.haml
@@ -1,6 +1,5 @@
.detail-page-description.milestone-detail
- %h2{ data: { qa_selector: "milestone_title_content" } }
- .title
+ %h2.title{ data: { qa_selector: "milestone_title_content" } }
= markdown_field(milestone, :title)
- if milestone.try(:description).present?
diff --git a/changelogs/unreleased/225948-replace-fa-info-circle-icons-with-gitlab-svg-information-icon.yml b/changelogs/unreleased/225948-replace-fa-info-circle-icons-with-gitlab-svg-information-icon.yml
new file mode 100644
index 00000000000..39bb430d67b
--- /dev/null
+++ b/changelogs/unreleased/225948-replace-fa-info-circle-icons-with-gitlab-svg-information-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Replace fa-info-circle icons with GitLab SVG information-o icon
+merge_request: 41721
+author:
+type: changed
diff --git a/changelogs/unreleased/243758-indent-two-spaced-lists.yml b/changelogs/unreleased/243758-indent-two-spaced-lists.yml
new file mode 100644
index 00000000000..423b848910d
--- /dev/null
+++ b/changelogs/unreleased/243758-indent-two-spaced-lists.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Static Site Editor Flattens Mixed Lists
+merge_request: 41599
+author:
+type: fixed
diff --git a/changelogs/unreleased/app-logger-6.yml b/changelogs/unreleased/app-logger-6.yml
new file mode 100644
index 00000000000..c35a211f60b
--- /dev/null
+++ b/changelogs/unreleased/app-logger-6.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in lib/gitlab/
+merge_request: 41052
+author: Rajendra Kadam
+type: other
diff --git a/changelogs/unreleased/app-logger-7.yml b/changelogs/unreleased/app-logger-7.yml
new file mode 100644
index 00000000000..4da303d905e
--- /dev/null
+++ b/changelogs/unreleased/app-logger-7.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in spec/lib/ee/gitlab/
+merge_request: 41053
+author: Rajendra Kadam
+type: other
diff --git a/changelogs/unreleased/app-logger-9.yml b/changelogs/unreleased/app-logger-9.yml
new file mode 100644
index 00000000000..8645c889c9f
--- /dev/null
+++ b/changelogs/unreleased/app-logger-9.yml
@@ -0,0 +1,5 @@
+---
+title: Use applogger in some files of ee/lib/* and spec files
+merge_request: 41056
+author: Rajendra Kadam
+type: other
diff --git a/changelogs/unreleased/ph-225410-commentHasWrongAuthorName.yml b/changelogs/unreleased/ph-225410-commentHasWrongAuthorName.yml
new file mode 100644
index 00000000000..2d166421951
--- /dev/null
+++ b/changelogs/unreleased/ph-225410-commentHasWrongAuthorName.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed note having wrong author after deleting
+merge_request: 41747
+author:
+type: fixed
diff --git a/changelogs/unreleased/psi-milestone-header-fix.yml b/changelogs/unreleased/psi-milestone-header-fix.yml
new file mode 100644
index 00000000000..59c250df5a6
--- /dev/null
+++ b/changelogs/unreleased/psi-milestone-header-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Remove excess space above milestone titles
+merge_request: 41749
+author:
+type: fixed
diff --git a/doc/development/feature_flags/development.md b/doc/development/feature_flags/development.md
index bdce0c29eea..bf7fee90600 100644
--- a/doc/development/feature_flags/development.md
+++ b/doc/development/feature_flags/development.md
@@ -25,9 +25,7 @@ This document is the subject of continued work as part of an epic to [improve in
## Types of feature flags
-Currently, only a single type of feature flag is available.
-Additional feature flag types will be provided in the future,
-with descriptions for their usage.
+Choose a feature flag type that matches the expected usage.
### `development` type
@@ -40,6 +38,29 @@ ideally created using the [Feature Flag Roll Out template](https://gitlab.com/gi
NOTE: **Note:**
This is the default type used when calling `Feature.enabled?`.
+### `ops` type
+
+`ops` feature flags are long-lived feature flags that control operational aspects
+of GitLab's behavior. For example, feature flags that disable features that might
+have a performance impact, like special Sidekiq worker behavior.
+
+`ops` feature flags likely do not have rollout issues, as it is hard to
+predict when they will be enabled or disabled.
+
+To use `ops` feature flags, you must append `type: :ops` to `Feature.enabled?`
+invocations:
+
+```ruby
+# Check if feature flag is enabled
+Feature.enabled?(:my_ops_flag, project, type: ops)
+
+# Check if feature flag is disabled
+Feature.disabled?(:my_ops_flag, project, type: ops)
+
+# Push feature flag to Frontend
+push_frontend_feature_flag(:my_ops_flag, project, type: :ops)
+```
+
## Feature flag definition and validation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229161) in GitLab 13.3.
@@ -147,6 +168,21 @@ if Feature.disabled?(:my_feature_flag, project, default_enabled: true)
end
```
+If not specified, the default feature flag type for `Feature.enabled?` and `Feature.disabled?`
+is `type: development`. For all other feature flag types, you must specify the `type:`:
+
+```ruby
+if Feature.enabled?(:feature_flag, project, type: :ops)
+ # execute code if ops feature flag is enabled
+else
+ # execute code if ops feature flag is disabled
+end
+
+if Feature.disabled?(:my_feature_flag, project, type: :ops)
+ # execute code if feature flag is disabled
+end
+```
+
### Frontend
Use the `push_frontend_feature_flag` method for frontend code, which is
@@ -192,6 +228,15 @@ before_action do
end
```
+If not specified, the default feature flag type for `push_frontend_feature_flag`
+is `type: development`. For all other feature flag types, you must specify the `type:`:
+
+```ruby
+before_action do
+ push_frontend_feature_flag(:vim_bindings, project, type: :ops)
+end
+```
+
### Feature actors
**It is strongly advised to use actors with feature flags.** Actors provide a simple
diff --git a/doc/user/project/repository/repository_mirroring.md b/doc/user/project/repository/repository_mirroring.md
index 9c644a1690e..e1d2c20850b 100644
--- a/doc/user/project/repository/repository_mirroring.md
+++ b/doc/user/project/repository/repository_mirroring.md
@@ -11,7 +11,8 @@ Repository mirroring allows for mirroring of repositories to and from external s
used to mirror branches, tags, and commits between repositories.
A repository mirror at GitLab will be updated automatically. You can also manually trigger an update
-at most once every 5 minutes.
+at most once every 5 minutes. Follow [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/237891)
+for discussions on how to potentially reduce the delay.
## Overview
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index b811e11a75c..1912a06682e 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -532,7 +532,7 @@ module API
::Gitlab::Tracking.event(category, action.to_s, **args)
rescue => error
- Rails.logger.warn( # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.warn(
"Tracking event failed for action: #{action}, category: #{category}, message: #{error.message}"
)
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index b7ce1eba3f9..69b53ea6c2f 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -67,7 +67,7 @@ module API
result == 'PONG'
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
false
end
diff --git a/lib/feature/shared.rb b/lib/feature/shared.rb
index 14efbb07100..53f027e3893 100644
--- a/lib/feature/shared.rb
+++ b/lib/feature/shared.rb
@@ -15,8 +15,18 @@ class Feature
optional: true,
rollout_issue: true,
example: <<-EOS
- Feature.enabled?(:my_feature_flag)
- Feature.enabled?(:my_feature_flag, type: :development)
+ Feature.enabled?(:my_feature_flag, project)
+ Feature.enabled?(:my_feature_flag, project, type: :development)
+ push_frontend_feature_flag?(:my_feature_flag, project)
+ EOS
+ },
+ ops: {
+ description: "Long-lived feature flags that control operational aspects of GitLab's behavior",
+ optional: true,
+ rollout_issue: false,
+ example: <<-EOS
+ Feature.enabled?(:my_ops_flag, type: ops)
+ push_frontend_feature_flag?(:my_ops_flag, project, type: :ops)
EOS
}
}.freeze
diff --git a/lib/gitlab/metrics/methods.rb b/lib/gitlab/metrics/methods.rb
index 2b5d1c710f6..3100450bc00 100644
--- a/lib/gitlab/metrics/methods.rb
+++ b/lib/gitlab/metrics/methods.rb
@@ -52,7 +52,7 @@ module Gitlab
end
def disabled_by_feature(options)
- options.with_feature && !::Feature.enabled?(options.with_feature)
+ options.with_feature && !::Feature.enabled?(options.with_feature, type: :ops)
end
def build_metric!(type, name, options)
diff --git a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
index 0dc53c61e84..5efd1b34d32 100644
--- a/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
+++ b/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job.rb
@@ -71,7 +71,7 @@ module Gitlab
end
def droppable?
- idempotent? && ::Feature.disabled?("disable_#{queue_name}_deduplication")
+ idempotent? && ::Feature.disabled?("disable_#{queue_name}_deduplication", type: :ops)
end
def scheduled_at
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6a9ec5a011b..4f703dfb2c9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3321,6 +3321,9 @@ msgstr ""
msgid "Are you sure you want to revoke this nickname?"
msgstr ""
+msgid "Are you sure you want to revoke this personal access token? This action cannot be undone."
+msgstr ""
+
msgid "Are you sure you want to stop this environment?"
msgstr ""
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 43003fe1953..57ab7fb4480 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -21,7 +21,7 @@ module QA
end
view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
- element :linked_pipeline_button
+ element :expand_pipeline_button
element :child_pipeline
end
@@ -69,8 +69,10 @@ module QA
click_element(:job_link, text: job_name)
end
- def click_linked_job(project_name)
- click_element(:linked_pipeline_button, text: /#{project_name}/)
+ def expand_child_pipeline
+ within_element(:child_pipeline) do
+ click_element(:expand_pipeline_button)
+ end
end
def click_on_first_job
diff --git a/qa/qa/page/sub_menus/common.rb b/qa/qa/page/sub_menus/common.rb
index 0eb1b100bd7..2efeeb062e8 100644
--- a/qa/qa/page/sub_menus/common.rb
+++ b/qa/qa/page/sub_menus/common.rb
@@ -12,6 +12,8 @@ module QA
end
def within_sidebar
+ wait_for_requests
+
within_element(sidebar_element) do
yield
end
diff --git a/spec/frontend/packages/details/components/app_spec.js b/spec/frontend/packages/details/components/app_spec.js
index 2a60fd91e14..e82c74e56e5 100644
--- a/spec/frontend/packages/details/components/app_spec.js
+++ b/spec/frontend/packages/details/components/app_spec.js
@@ -34,12 +34,15 @@ describe('PackagesApp', () => {
let wrapper;
let store;
const fetchPackageVersions = jest.fn();
+ const deletePackage = jest.fn();
+ const defaultProjectName = 'bar';
+ const { location } = window;
function createComponent({
packageEntity = mavenPackage,
packageFiles = mavenFiles,
isLoading = false,
- oneColumnView = false,
+ projectName = defaultProjectName,
} = {}) {
store = new Vuex.Store({
state: {
@@ -47,14 +50,15 @@ describe('PackagesApp', () => {
packageEntity,
packageFiles,
canDelete: true,
- destroyPath: 'destroy-package-path',
emptySvgPath: 'empty-illustration',
npmPath: 'foo',
npmHelpPath: 'foo',
- projectName: 'bar',
- oneColumnView,
+ projectName,
+ projectListUrl: 'project_url',
+ groupListUrl: 'group_url',
},
actions: {
+ deletePackage,
fetchPackageVersions,
},
getters,
@@ -95,8 +99,14 @@ describe('PackagesApp', () => {
const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata);
const findInstallationCommands = () => wrapper.find(InstallationCommands);
+ beforeEach(() => {
+ delete window.location;
+ window.location = { replace: jest.fn() };
+ });
+
afterEach(() => {
wrapper.destroy();
+ window.location = location;
});
it('renders the app and displays the package title', () => {
@@ -240,44 +250,94 @@ describe('PackagesApp', () => {
});
});
- describe('tracking', () => {
- let eventSpy;
- let utilSpy;
- const category = 'foo';
+ describe('tracking and delete', () => {
+ const doDelete = async () => {
+ deleteButton().trigger('click');
+ await wrapper.vm.$nextTick();
+ modalDeleteButton().trigger('click');
+ };
+
+ describe('delete', () => {
+ const originalReferrer = document.referrer;
+ const setReferrer = (value = defaultProjectName) => {
+ Object.defineProperty(document, 'referrer', {
+ value,
+ configurable: true,
+ });
+ };
+
+ afterEach(() => {
+ Object.defineProperty(document, 'referrer', {
+ value: originalReferrer,
+ configurable: true,
+ });
+ });
- beforeEach(() => {
- eventSpy = jest.spyOn(Tracking, 'event');
- utilSpy = jest.spyOn(SharedUtils, 'packageTypeToTrackCategory').mockReturnValue(category);
- });
+ it('calls the proper vuex action', async () => {
+ createComponent({ packageEntity: npmPackage });
+ await doDelete();
+ expect(deletePackage).toHaveBeenCalled();
+ });
- it('tracking category calls packageTypeToTrackCategory', () => {
- createComponent({ packageEntity: conanPackage });
- expect(wrapper.vm.tracking.category).toBe(category);
- expect(utilSpy).toHaveBeenCalledWith('conan');
+ it('when referrer contains project name calls window.replace with project url', async () => {
+ setReferrer();
+ deletePackage.mockResolvedValue();
+ createComponent({ packageEntity: npmPackage });
+ await doDelete();
+ await deletePackage();
+ expect(window.location.replace).toHaveBeenCalledWith(
+ 'project_url?showSuccessDeleteAlert=true',
+ );
+ });
+
+ it('when referrer does not contain project name calls window.replace with group url', async () => {
+ setReferrer('baz');
+ deletePackage.mockResolvedValue();
+ createComponent({ packageEntity: npmPackage });
+ await doDelete();
+ await deletePackage();
+ expect(window.location.replace).toHaveBeenCalledWith(
+ 'group_url?showSuccessDeleteAlert=true',
+ );
+ });
});
- it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, () => {
- createComponent({ packageEntity: conanPackage });
- deleteButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- modalDeleteButton().trigger('click');
+ describe('tracking', () => {
+ let eventSpy;
+ let utilSpy;
+ const category = 'foo';
+
+ beforeEach(() => {
+ eventSpy = jest.spyOn(Tracking, 'event');
+ utilSpy = jest.spyOn(SharedUtils, 'packageTypeToTrackCategory').mockReturnValue(category);
+ });
+
+ it('tracking category calls packageTypeToTrackCategory', () => {
+ createComponent({ packageEntity: conanPackage });
+ expect(wrapper.vm.tracking.category).toBe(category);
+ expect(utilSpy).toHaveBeenCalledWith('conan');
+ });
+
+ it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, async () => {
+ createComponent({ packageEntity: npmPackage });
+ await doDelete();
expect(eventSpy).toHaveBeenCalledWith(
category,
TrackingActions.DELETE_PACKAGE,
expect.any(Object),
);
});
- });
- it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
- createComponent({ packageEntity: conanPackage });
+ it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
+ createComponent({ packageEntity: conanPackage });
- firstFileDownloadLink().vm.$emit('click');
- expect(eventSpy).toHaveBeenCalledWith(
- category,
- TrackingActions.PULL_PACKAGE,
- expect.any(Object),
- );
+ firstFileDownloadLink().vm.$emit('click');
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ TrackingActions.PULL_PACKAGE,
+ expect.any(Object),
+ );
+ });
});
});
});
diff --git a/spec/frontend/packages/details/store/actions_spec.js b/spec/frontend/packages/details/store/actions_spec.js
index 6dfb2b63f85..70f87d18bcb 100644
--- a/spec/frontend/packages/details/store/actions_spec.js
+++ b/spec/frontend/packages/details/store/actions_spec.js
@@ -1,9 +1,10 @@
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
-import fetchPackageVersions from '~/packages/details/store/actions';
+import { fetchPackageVersions, deletePackage } from '~/packages/details/store/actions';
import * as types from '~/packages/details/store/mutation_types';
import { FETCH_PACKAGE_VERSIONS_ERROR } from '~/packages/details/constants';
+import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/shared/constants';
import { npmPackage as packageEntity } from '../../mock_data';
jest.mock('~/flash.js');
@@ -73,4 +74,25 @@ describe('Actions Package details store', () => {
);
});
});
+
+ describe('deletePackage', () => {
+ it('should call Api.deleteProjectPackage', done => {
+ Api.deleteProjectPackage = jest.fn().mockResolvedValue();
+ testAction(deletePackage, undefined, { packageEntity }, [], [], () => {
+ expect(Api.deleteProjectPackage).toHaveBeenCalledWith(
+ packageEntity.project_id,
+ packageEntity.id,
+ );
+ done();
+ });
+ });
+ it('should create flash on API error', done => {
+ Api.deleteProjectPackage = jest.fn().mockRejectedValue();
+
+ testAction(deletePackage, undefined, { packageEntity }, [], [], () => {
+ expect(createFlash).toHaveBeenCalledWith(DELETE_PACKAGE_ERROR_MESSAGE);
+ done();
+ });
+ });
+ });
});
diff --git a/spec/frontend/packages/list/components/packages_list_app_spec.js b/spec/frontend/packages/list/components/packages_list_app_spec.js
index 31bab3886c1..19ff4290f50 100644
--- a/spec/frontend/packages/list/components/packages_list_app_spec.js
+++ b/spec/frontend/packages/list/components/packages_list_app_spec.js
@@ -1,7 +1,14 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlEmptyState, GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
+import * as commonUtils from '~/lib/utils/common_utils';
+import createFlash from '~/flash';
import PackageListApp from '~/packages/list/components/packages_list_app.vue';
+import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
+import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants';
+
+jest.mock('~/lib/utils/common_utils');
+jest.mock('~/flash');
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -145,4 +152,46 @@ describe('packages_list_app', () => {
);
});
});
+
+ describe('delete alert handling', () => {
+ const { location } = window.location;
+ const search = `?${SHOW_DELETE_SUCCESS_ALERT}=true`;
+
+ beforeEach(() => {
+ createStore('foo');
+ jest.spyOn(commonUtils, 'historyReplaceState').mockImplementation(() => {});
+ delete window.location;
+ window.location = {
+ href: `foo_bar_baz${search}`,
+ search,
+ };
+ });
+
+ afterEach(() => {
+ window.location = location;
+ });
+
+ it(`creates a flash if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
+ mountComponent();
+
+ expect(createFlash).toHaveBeenCalledWith({
+ message: DELETE_PACKAGE_SUCCESS_MESSAGE,
+ type: 'notice',
+ });
+ });
+
+ it('calls historyReplaceState with a clean url', () => {
+ mountComponent();
+
+ expect(commonUtils.historyReplaceState).toHaveBeenCalledWith('foo_bar_baz');
+ });
+
+ it(`does nothing if the query string does not contain ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
+ window.location.search = '';
+ mountComponent();
+
+ expect(createFlash).not.toHaveBeenCalled();
+ expect(commonUtils.historyReplaceState).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/packages/list/stores/actions_spec.js b/spec/frontend/packages/list/stores/actions_spec.js
index faa629cc01f..cf205ecbac4 100644
--- a/spec/frontend/packages/list/stores/actions_spec.js
+++ b/spec/frontend/packages/list/stores/actions_spec.js
@@ -5,7 +5,8 @@ import Api from '~/api';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import * as actions from '~/packages/list/stores/actions';
import * as types from '~/packages/list/stores/mutation_types';
-import { MISSING_DELETE_PATH_ERROR, DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/list/constants';
+import { MISSING_DELETE_PATH_ERROR } from '~/packages/list/constants';
+import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages/shared/constants';
jest.mock('~/flash.js');
jest.mock('~/api.js');
diff --git a/spec/frontend/static_site_editor/services/formatter_spec.js b/spec/frontend/static_site_editor/services/formatter_spec.js
index b7600798db9..9e9c4bbd171 100644
--- a/spec/frontend/static_site_editor/services/formatter_spec.js
+++ b/spec/frontend/static_site_editor/services/formatter_spec.js
@@ -1,6 +1,6 @@
import formatter from '~/static_site_editor/services/formatter';
-describe('formatter', () => {
+describe('static_site_editor/services/formatter', () => {
const source = `Some text
<br>
@@ -23,4 +23,17 @@ And even more text`;
it('removes extraneous <br> tags', () => {
expect(formatter(source)).toMatch(sourceWithoutBrTags);
});
+
+ describe('ordered lists with incorrect content indentation', () => {
+ it.each`
+ input | result
+ ${'12. ordered list item\n13.Next ordered list item'} | ${'12. ordered list item\n13.Next ordered list item'}
+ ${'12. ordered list item\n - Next ordered list item'} | ${'12. ordered list item\n - Next ordered list item'}
+ ${'12. ordered list item\n - Next ordered list item'} | ${'12. ordered list item\n - Next ordered list item'}
+ ${'12. ordered list item\n Next ordered list item'} | ${'12. ordered list item\n Next ordered list item'}
+ ${'1. ordered list item\n Next ordered list item'} | ${'1. ordered list item\n Next ordered list item'}
+ `('\ntransforms\n$input \nto\n$result', ({ input, result }) => {
+ expect(formatter(input)).toBe(result);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
index 48653bce0fd..fd745c21bb6 100644
--- a/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
+++ b/spec/frontend/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer_spec.js
@@ -178,7 +178,9 @@ describe('rich_content_editor/services/html_to_markdown_renderer', () => {
});
it('returns raw text when pre node has sse-reference-definitions class', () => {
- expect(htmlToMarkdownRenderer['PRE CODE'](node, subContent)).toBe(`\n\n${node.innerText}\n`);
+ expect(htmlToMarkdownRenderer['PRE CODE'](node, subContent)).toBe(
+ `\n\n${node.innerText}\n\n`,
+ );
});
it('returns base result when pre node does not have sse-reference-definitions class', () => {
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index ccfde95b00c..51a45dff6a4 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -183,7 +183,7 @@ RSpec.describe API::Helpers do
end
it "logs an exception" do
- expect(Rails.logger).to receive(:warn).with(/Tracking event failed/)
+ expect(Gitlab::AppLogger).to receive(:warn).with(/Tracking event failed/)
subject.track_event('my_event', category: nil)
end