diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-09 15:08:47 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-09 15:08:47 +0000 |
commit | 825e4190a30a745adb37ab822eb8f9d845b462e7 (patch) | |
tree | 2588f929e0dc2bd97bf9b57eee596ae65315144c | |
parent | d022b7432efd720f0cf5f8d2a2cdaac7619bab57 (diff) | |
download | gitlab-ce-825e4190a30a745adb37ab822eb8f9d845b462e7.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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 |