diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-13 18:08:38 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-13 18:08:38 +0300 |
commit | e1189e4c3b8bd5535104f458f5af7505789eac00 (patch) | |
tree | 1a3e2e536e66120c60a6088329349f323de2d14b /app | |
parent | 8ce82c1eaf87d1b22b645e2c37671f01d2c35581 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
41 files changed, 501 insertions, 193 deletions
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index cc7262f3a39..69abf886ad7 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -41,7 +41,7 @@ export default { watch: { filterParams: { handler() { - if (this.list.id) { + if (this.list.id && !this.list.collapsed) { this.fetchItemsForList({ listId: this.list.id }); } }, diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 5c06f284bb7..b52a9a71ae8 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -240,7 +240,7 @@ export default { }, updateList: ( - { commit, state: { issuableType } }, + { commit, state: { issuableType, boardItemsByListId = {} }, dispatch }, { listId, position, collapsed, backupList }, ) => { gqlClient @@ -255,6 +255,12 @@ export default { .then(({ data }) => { if (data?.updateBoardList?.errors.length) { commit(types.UPDATE_LIST_FAILURE, backupList); + return; + } + + // Only fetch when board items havent been fetched on a collapsed list + if (!boardItemsByListId[listId]) { + dispatch('fetchItemsForList', { listId }); } }) .catch(() => { diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js index 8bcfb490749..4dd8a1376ad 100644 --- a/app/assets/javascripts/content_editor/extensions/image.js +++ b/app/assets/javascripts/content_editor/extensions/image.js @@ -1,10 +1,65 @@ import { Image } from '@tiptap/extension-image'; -import { defaultMarkdownSerializer } from 'prosemirror-markdown/src/to_markdown'; +import { VueNodeViewRenderer } from '@tiptap/vue-2'; +import { Plugin, PluginKey } from 'prosemirror-state'; +import { __ } from '~/locale'; +import ImageWrapper from '../components/wrappers/image.vue'; +import { uploadFile } from '../services/upload_file'; +import { getImageAlt, readFileAsDataURL } from '../services/utils'; + +export const acceptedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/jpg']; + +const resolveImageEl = (element) => + element.nodeName === 'IMG' ? element : element.querySelector('img'); + +const startFileUpload = async ({ editor, file, uploadsPath, renderMarkdown }) => { + const encodedSrc = await readFileAsDataURL(file); + const { view } = editor; + + editor.commands.setImage({ uploading: true, src: encodedSrc }); + + const { state } = view; + const position = state.selection.from - 1; + const { tr } = state; + + try { + const { src, canonicalSrc } = await uploadFile({ file, uploadsPath, renderMarkdown }); + + view.dispatch( + tr.setNodeMarkup(position, undefined, { + uploading: false, + src: encodedSrc, + alt: getImageAlt(src), + canonicalSrc, + }), + ); + } catch (e) { + editor.commands.deleteRange({ from: position, to: position + 1 }); + editor.emit('error', __('An error occurred while uploading the image. Please try again.')); + } +}; + +const handleFileEvent = ({ editor, file, uploadsPath, renderMarkdown }) => { + if (acceptedMimes.includes(file?.type)) { + startFileUpload({ editor, file, uploadsPath, renderMarkdown }); + + return true; + } + + return false; +}; const ExtendedImage = Image.extend({ + defaultOptions: { + ...Image.options, + uploadsPath: null, + renderMarkdown: null, + }, addAttributes() { return { ...this.parent?.(), + uploading: { + default: false, + }, src: { default: null, /* @@ -14,17 +69,25 @@ const ExtendedImage = Image.extend({ * attribute. */ parseHTML: (element) => { - const img = element.querySelector('img'); + const img = resolveImageEl(element); return { src: img.dataset.src || img.getAttribute('src'), }; }, }, + canonicalSrc: { + default: null, + parseHTML: (element) => { + return { + canonicalSrc: element.dataset.canonicalSrc, + }; + }, + }, alt: { default: null, parseHTML: (element) => { - const img = element.querySelector('img'); + const img = resolveImageEl(element); return { alt: img.getAttribute('alt'), @@ -44,9 +107,58 @@ const ExtendedImage = Image.extend({ }, ]; }, + addCommands() { + return { + ...this.parent(), + uploadImage: ({ file }) => () => { + const { uploadsPath, renderMarkdown } = this.options; + + handleFileEvent({ file, uploadsPath, renderMarkdown, editor: this.editor }); + }, + }; + }, + addProseMirrorPlugins() { + const { editor } = this; + + return [ + new Plugin({ + key: new PluginKey('handleDropAndPasteImages'), + props: { + handlePaste: (_, event) => { + const { uploadsPath, renderMarkdown } = this.options; + + return handleFileEvent({ + editor, + file: event.clipboardData.files[0], + uploadsPath, + renderMarkdown, + }); + }, + handleDrop: (_, event) => { + const { uploadsPath, renderMarkdown } = this.options; + + return handleFileEvent({ + editor, + file: event.dataTransfer.files[0], + uploadsPath, + renderMarkdown, + }); + }, + }, + }), + ]; + }, + addNodeView() { + return VueNodeViewRenderer(ImageWrapper); + }, }); -const serializer = defaultMarkdownSerializer.nodes.image; +const serializer = (state, node) => { + const { alt, canonicalSrc, src, title } = node.attrs; + const quotedTitle = title ? ` ${state.quote(title)}` : ''; + + state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`); +}; export const configure = ({ renderMarkdown, uploadsPath }) => { return { diff --git a/app/assets/javascripts/content_editor/services/utils.js b/app/assets/javascripts/content_editor/services/utils.js index cf5234bbff8..ca2f9762ff8 100644 --- a/app/assets/javascripts/content_editor/services/utils.js +++ b/app/assets/javascripts/content_editor/services/utils.js @@ -3,3 +3,15 @@ export const hasSelection = (tiptapEditor) => { return from < to; }; + +export const getImageAlt = (src) => { + return src.replace(/^.*\/|\..*$/g, '').replace(/\W+/g, ' '); +}; + +export const readFileAsDataURL = (file) => { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.addEventListener('load', (e) => resolve(e.target.result), { once: true }); + reader.readAsDataURL(file); + }); +}; diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue index 833d7081a2c..1e1f5135290 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue @@ -1,6 +1,7 @@ <script> import { GlTooltipDirective, GlIcon, GlLink, GlSafeHtmlDirective } from '@gitlab/ui'; import { ApolloMutation } from 'vue-apollo'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { __ } from '~/locale'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -48,6 +49,9 @@ export default { author() { return this.note.author; }, + authorId() { + return getIdFromGraphQLId(this.author.id); + }, noteAnchorId() { return findNoteId(this.note.id); }, @@ -94,7 +98,7 @@ export default { v-once :href="author.webUrl" class="js-user-link" - :data-user-id="author.id" + :data-user-id="authorId" :data-username="author.username" > <span class="note-header-author-name gl-font-weight-bold">{{ author.name }}</span> diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index d91fc61ba21..0804213cafa 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -97,7 +97,7 @@ export default (resolvers = {}, config = {}) => { */ const fetchIntervention = (url, options) => { - return fetch(stripWhitespaceFromQuery(url, path), options); + return fetch(stripWhitespaceFromQuery(url, uri), options); }; const requestLink = ApolloLink.split( diff --git a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue index 63e3f62c0c6..33d86dec767 100644 --- a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue +++ b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue @@ -1,8 +1,7 @@ <script> import { GlFilteredSearchToken } from '@gitlab/ui'; import { mapState } from 'vuex'; -// eslint-disable-next-line import/no-deprecated -import { getParameterByName, setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility'; +import { getParameterByName, setUrlParams, queryToObject } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import { SEARCH_TOKEN_TYPE, @@ -68,8 +67,7 @@ export default { }, }, created() { - // eslint-disable-next-line import/no-deprecated - const query = urlParamsToObject(window.location.search); + const query = queryToObject(window.location.search); const tokens = this.tokens .filter((token) => query[token.type]) diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 47d0c26b106..29c60b96d8a 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -66,6 +66,7 @@ export default { data() { return { currentFilter: null, + renderSkeleton: !this.shouldShow, }; }, computed: { @@ -93,7 +94,7 @@ export default { return this.noteableData.noteableType; }, allDiscussions() { - if (this.isLoading) { + if (this.renderSkeleton || this.isLoading) { const prerenderedNotesCount = parseInt(this.notesData.prerenderedNotesCount, 10) || 0; return new Array(prerenderedNotesCount).fill({ @@ -122,6 +123,10 @@ export default { if (!this.isNotesFetched) { this.fetchNotes(); } + + setTimeout(() => { + this.renderSkeleton = !this.shouldShow; + }); }, discussionTabCounterText(val) { if (this.discussionsCount) { diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 04ea6f760f6..ee02f446795 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -74,6 +74,7 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => { const bindEvents = () => { const $newProjectForm = $('#new_project'); const $projectImportUrl = $('#project_import_url'); + const $projectImportUrlWarning = $('.js-import-url-warning'); const $projectPath = $('.tab-pane.active #project_path'); const $useTemplateBtn = $('.template-button > input'); const $projectFieldsForm = $('.project-fields-form'); @@ -134,7 +135,25 @@ const bindEvents = () => { $projectPath.val($projectPath.val().trim()); }); - $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl)); + function updateUrlPathWarningVisibility() { + const url = $projectImportUrl.val(); + const URL_PATTERN = /(?:git|https?):\/\/.*\/.*\.git$/; + const isUrlValid = URL_PATTERN.test(url); + $projectImportUrlWarning.toggleClass('hide', isUrlValid); + } + + let isProjectImportUrlDirty = false; + $projectImportUrl.on('blur', () => { + isProjectImportUrlDirty = true; + updateUrlPathWarningVisibility(); + }); + $projectImportUrl.on('keyup', () => { + deriveProjectPathFromUrl($projectImportUrl); + // defer error message till first input blur + if (isProjectImportUrlDirty) { + updateUrlPathWarningVisibility(); + } + }); $('.js-import-git-toggle-button').on('click', () => { const $projectMirror = $('#project_mirror'); diff --git a/app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue b/app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue new file mode 100644 index 00000000000..48fa33eb558 --- /dev/null +++ b/app/assets/javascripts/repository/components/blob_viewers/download_viewer.vue @@ -0,0 +1,51 @@ +<script> +import { GlIcon, GlLink } from '@gitlab/ui'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; +import { sprintf, __ } from '~/locale'; + +export default { + components: { + GlIcon, + GlLink, + }, + props: { + fileName: { + type: String, + required: true, + }, + filePath: { + type: String, + required: true, + }, + fileSize: { + type: Number, + required: false, + default: 0, + }, + }, + computed: { + downloadFileSize() { + return numberToHumanSize(this.fileSize); + }, + downloadText() { + if (this.fileSize > 0) { + return sprintf(__('Download (%{fileSizeReadable})'), { + fileSizeReadable: this.downloadFileSize, + }); + } + return __('Download'); + }, + }, +}; +</script> + +<template> + <div class="gl-text-center gl-py-13 gl-bg-gray-50"> + <gl-link :href="filePath" rel="nofollow" :download="fileName" target="_blank"> + <div> + <gl-icon :size="16" name="download" class="gl-text-gray-900" /> + </div> + <h4>{{ downloadText }}</h4> + </gl-link> + </div> +</template> diff --git a/app/assets/javascripts/repository/components/blob_viewers/index.js b/app/assets/javascripts/repository/components/blob_viewers/index.js index abe274d1568..4e16b16041f 100644 --- a/app/assets/javascripts/repository/components/blob_viewers/index.js +++ b/app/assets/javascripts/repository/components/blob_viewers/index.js @@ -5,8 +5,7 @@ export const loadViewer = (type) => { case 'text': return () => import(/* webpackChunkName: 'blob_text_viewer' */ './text_viewer.vue'); case 'download': - // TODO (follow-up): import the download viewer - return null; // () => import(/* webpackChunkName: 'blob_download_viewer' */ './download_viewer.vue'); + return () => import(/* webpackChunkName: 'blob_download_viewer' */ './download_viewer.vue'); default: return null; } @@ -19,5 +18,10 @@ export const viewerProps = (type, blob) => { fileName: blob.name, readOnly: true, }, + download: { + fileName: blob.name, + filePath: blob.rawPath, + fileSize: blob.rawSize, + }, }[type]; }; diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 82d768c2351..5e2ec774655 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -87,6 +87,12 @@ padding-bottom: $gl-spacing-scale-8; } +// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1495 +.gl-py-13 { + padding-top: $gl-spacing-scale-13; + padding-bottom: $gl-spacing-scale-13; +} + .gl-transition-property-stroke-opacity { transition-property: stroke-opacity; } diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 83ede2867cc..4328f3f7a4b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -325,7 +325,11 @@ module Ci build.run_after_commit do build.run_status_commit_hooks! - BuildFinishedWorker.perform_async(id) + if Feature.enabled?(:ci_build_finished_worker_namespace_changed, build.project, default_enabled: :yaml) + Ci::BuildFinishedWorker.perform_async(id) + else + ::BuildFinishedWorker.perform_async(id) + end end end diff --git a/app/models/ci/pending_build.rb b/app/models/ci/pending_build.rb index a1eaae8a21a..0663052f51d 100644 --- a/app/models/ci/pending_build.rb +++ b/app/models/ci/pending_build.rb @@ -11,11 +11,48 @@ module Ci scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) } def self.upsert_from_build!(build) - entry = self.new(build: build, project: build.project, protected: build.protected?) + entry = self.new(args_from_build(build)) entry.validate! self.upsert(entry.attributes.compact, returning: %w[build_id], unique_by: :build_id) end + + def self.args_from_build(build) + args = { + build: build, + project: build.project, + protected: build.protected? + } + + if Feature.enabled?(:ci_pending_builds_maintain_shared_runners_data, type: :development, default_enabled: :yaml) + args.merge(instance_runners_enabled: shareable?(build)) + else + args + end + end + private_class_method :args_from_build + + def self.shareable?(build) + shared_runner_enabled?(build) && + builds_access_level?(build) && + project_not_removed?(build) + end + private_class_method :shareable? + + def self.shared_runner_enabled?(build) + build.project.shared_runners.exists? + end + private_class_method :shared_runner_enabled? + + def self.project_not_removed?(build) + !build.project.pending_delete? + end + private_class_method :project_not_removed? + + def self.builds_access_level?(build) + build.project.project_feature.builds_access_level.nil? || build.project.project_feature.builds_access_level > 0 + end + private_class_method :builds_access_level? end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 6b8c5d21345..5d079f57267 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -224,7 +224,7 @@ module Ci end after_transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success do |pipeline| - # We wait a little bit to ensure that all BuildFinishedWorkers finish first + # We wait a little bit to ensure that all Ci::BuildFinishedWorkers finish first # because this is where some metrics like code coverage is parsed and stored # in CI build records which the daily build metrics worker relies on. pipeline.run_after_commit { Ci::DailyBuildGroupReportResultsWorker.perform_in(10.minutes, pipeline.id) } diff --git a/app/models/concerns/partitioned_table.rb b/app/models/concerns/partitioned_table.rb index 9f1cec5d520..eab5d4c35bb 100644 --- a/app/models/concerns/partitioned_table.rb +++ b/app/models/concerns/partitioned_table.rb @@ -10,12 +10,12 @@ module PartitionedTable monthly: Gitlab::Database::Partitioning::MonthlyStrategy }.freeze - def partitioned_by(partitioning_key, strategy:) + def partitioned_by(partitioning_key, strategy:, **kwargs) strategy_class = PARTITIONING_STRATEGIES[strategy.to_sym] || raise(ArgumentError, "Unknown partitioning strategy: #{strategy}") - @partitioning_strategy = strategy_class.new(self, partitioning_key) + @partitioning_strategy = strategy_class.new(self, partitioning_key, **kwargs) - Gitlab::Database::Partitioning::PartitionCreator.register(self) + Gitlab::Database::Partitioning::PartitionManager.register(self) end end end diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index 0c96d5d4b6d..8c0565e4a38 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -9,7 +9,7 @@ class WebHookLog < ApplicationRecord self.primary_key = :id - partitioned_by :created_at, strategy: :monthly + partitioned_by :created_at, strategy: :monthly, retain_for: 3.months belongs_to :web_hook diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb index 518d061c654..966d04ceb70 100644 --- a/app/services/groups/transfer_service.rb +++ b/app/services/groups/transfer_service.rb @@ -46,6 +46,7 @@ module Groups def ensure_allowed_transfer raise_transfer_error(:group_is_already_root) if group_is_already_root? raise_transfer_error(:same_parent_as_current) if same_parent? + raise_transfer_error(:has_subscription) if has_subscription? raise_transfer_error(:invalid_policies) unless valid_policies? raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path? raise_transfer_error(:group_contains_images) if group_projects_contain_registry_images? @@ -73,6 +74,10 @@ module Groups @new_parent_group && @new_parent_group.id == @group.parent_id end + def has_subscription? + @group.paid? + end + def transfer_to_subgroup? @new_parent_group && \ @group.self_and_descendants.pluck_primary_key.include?(@new_parent_group.id) diff --git a/app/services/service_ping/permit_data_categories_service.rb b/app/services/service_ping/permit_data_categories_service.rb new file mode 100644 index 00000000000..ff48c022b56 --- /dev/null +++ b/app/services/service_ping/permit_data_categories_service.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ServicePing + class PermitDataCategoriesService + STANDARD_CATEGORY = 'Standard' + SUBSCRIPTION_CATEGORY = 'Subscription' + OPERATIONAL_CATEGORY = 'Operational' + OPTIONAL_CATEGORY = 'Optional' + CATEGORIES = [ + STANDARD_CATEGORY, + SUBSCRIPTION_CATEGORY, + OPERATIONAL_CATEGORY, + OPTIONAL_CATEGORY + ].to_set.freeze + + def execute + return [] unless product_intelligence_enabled? + + CATEGORIES + end + + def product_intelligence_enabled? + pings_enabled? && !User.single_user&.requires_usage_stats_consent? + end + + private + + def pings_enabled? + ::Gitlab::CurrentSettings.usage_ping_enabled? + end + end +end + +ServicePing::PermitDataCategoriesService.prepend_mod_with('ServicePing::PermitDataCategoriesService') diff --git a/app/services/service_ping/submit_service.rb b/app/services/service_ping/submit_service.rb index f12f9c43109..06e4fcbaf32 100644 --- a/app/services/service_ping/submit_service.rb +++ b/app/services/service_ping/submit_service.rb @@ -18,8 +18,7 @@ module ServicePing SubmissionError = Class.new(StandardError) def execute - return unless Gitlab::CurrentSettings.usage_ping_enabled? - return if User.single_user&.requires_usage_stats_consent? + return unless ServicePing::PermitDataCategoriesService.new.product_intelligence_enabled? usage_data = Gitlab::UsageData.data(force_refresh: true) diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml index d7a145924de..fea0736ffc8 100644 --- a/app/views/groups/settings/_advanced.html.haml +++ b/app/views/groups/settings/_advanced.html.haml @@ -24,21 +24,6 @@ "data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}" = f.submit s_('GroupSettings|Change group URL'), class: 'btn gl-button btn-warning' -.sub-section - %h4.warning-title= s_('GroupSettings|Transfer group') - = form_for @group, url: transfer_group_path(@group), method: :put, html: { class: 'js-group-transfer-form' } do |f| - .form-group - = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group), qa_selector: 'select_group_dropdown' } }) - = hidden_field_tag 'new_parent_group_id' - - %ul - - side_effects_link_start = '<a href="https://docs.gitlab.com/ee/user/project/index.html#redirects-when-changing-repository-paths" target="_blank">'.html_safe - - warning_text = s_("GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}.") % { side_effects_link_start: side_effects_link_start, side_effects_link_end: '</a>'.html_safe } - %li= warning_text.html_safe - %li= s_('GroupSettings|You can only transfer the group to a group you manage.') - %li= s_('GroupSettings|You will need to update your local repositories to point to the new location.') - %li= s_("GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.") - = f.submit s_('GroupSettings|Transfer group'), class: 'btn gl-button btn-warning', data: { qa_selector: "transfer_group_button" } - += render 'groups/settings/transfer', group: @group = render 'groups/settings/remove', group: @group = render_if_exists 'groups/settings/restore', group: @group diff --git a/app/views/groups/settings/_transfer.html.haml b/app/views/groups/settings/_transfer.html.haml new file mode 100644 index 00000000000..1472ae42152 --- /dev/null +++ b/app/views/groups/settings/_transfer.html.haml @@ -0,0 +1,22 @@ +.sub-section + %h4.warning-title= s_('GroupSettings|Transfer group') + = form_for group, url: transfer_group_path(group), method: :put, html: { class: 'js-group-transfer-form' } do |f| + .form-group + = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', disabled: group.paid?, data: { data: parent_group_options(group), qa_selector: 'select_group_dropdown' } }) + = hidden_field_tag 'new_parent_group_id' + + %ul + - side_effects_link_start = '<a href="https://docs.gitlab.com/ee/user/project/index.html#redirects-when-changing-repository-paths" target="_blank">'.html_safe + - warning_text = s_("GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}.") % { side_effects_link_start: side_effects_link_start, side_effects_link_end: '</a>'.html_safe } + %li= warning_text.html_safe + %li= s_('GroupSettings|You can only transfer the group to a group you manage.') + %li= s_('GroupSettings|You will need to update your local repositories to point to the new location.') + %li= s_("GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.") + + - if group.paid? + .gl-alert.gl-alert-info.gl-mb-5{ data: { testid: 'group-to-transfer-has-linked-subscription-alert' } } + = sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title') + .gl-alert-body + = html_escape(_("This group can't be transfered because it is linked to a subscription. To transfer this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/index', anchor: 'change-the-linked-namespace')}\">".html_safe, linkEnd: '</a>'.html_safe } + + = f.submit s_('GroupSettings|Transfer group'), class: 'btn gl-button btn-warning', data: { qa_selector: "transfer_group_button" } diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 3acc41f04b7..5c9c6a06ac1 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -14,7 +14,7 @@ .row.mt-3 .col-sm-12 %h1.mb-3.font-weight-normal - = current_appearance&.title.presence || "GitLab" + = current_appearance&.title.presence || _('GitLab') .row.mb-3 .col-sm-7.order-12.order-sm-1.brand-holder - unless recently_confirmed_com? diff --git a/app/views/search/results/_blob_data.html.haml b/app/views/search/results/_blob_data.html.haml index 16d640273b0..fb2825ad15e 100644 --- a/app/views/search/results/_blob_data.html.haml +++ b/app/views/search/results/_blob_data.html.haml @@ -5,6 +5,7 @@ = sprite_icon('document') %strong = search_blob_title(project, path) + = copy_file_path_button(path) - if blob.data .file-content.code.term{ data: { qa_selector: 'file_text_content' } } = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link, highlight_line: blob.highlight_line diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 65e02341936..f03314563cb 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -8,7 +8,18 @@ = _('Git repository URL') = f.text_field :import_url, value: import_url.sanitized_url, autocomplete: 'off', class: 'form-control gl-form-input', placeholder: 'https://gitlab.company.com/group/project.git', required: true + = render 'shared/global_alert', + variant: :warning, + alert_class: 'gl-mt-3 js-import-url-warning hide', + dismissible: false, + close_button_class: 'js-close-2fa-enabled-success-alert' do + .gl-alert-body + = s_('Import|A repository URL usually ends in a .git suffix, although this is not required. Double check to make sure your repository URL is correct.') + .gl-alert.gl-alert-not-dismissible.gl-alert-warning.gl-mt-3.hide#project_import_url_warning + .gl-alert-container + = sprite_icon('warning-solid', css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title') + .gl-alert-content{ role: 'alert' } .row .form-group.col-md-6 = f.label :import_url_user, class: 'label-bold' do diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 04c557965b1..de1b17b588f 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -247,6 +247,15 @@ :idempotent: true :tags: - :exclude_from_kubernetes +- :name: cronjob:database_partition_management + :worker_name: Database::PartitionManagementWorker + :feature_category: :database + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: cronjob:environments_auto_stop_cron :worker_name: Environments::AutoStopCronWorker :feature_category: :continuous_delivery @@ -1365,6 +1374,15 @@ :weight: 1 :idempotent: :tags: [] +- :name: pipeline_background:ci_archive_trace + :worker_name: Ci::ArchiveTraceWorker + :feature_category: :continuous_integration + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: + :tags: [] - :name: pipeline_background:ci_build_trace_chunk_flush :worker_name: Ci::BuildTraceChunkFlushWorker :feature_category: :continuous_integration @@ -1585,6 +1603,15 @@ :weight: 5 :idempotent: :tags: [] +- :name: pipeline_processing:ci_build_finished + :worker_name: Ci::BuildFinishedWorker + :feature_category: :continuous_integration + :has_external_dependencies: + :urgency: :high + :resource_boundary: :cpu + :weight: 5 + :idempotent: + :tags: [] - :name: pipeline_processing:ci_build_prepare :worker_name: Ci::BuildPrepareWorker :feature_category: :continuous_integration diff --git a/app/workers/archive_trace_worker.rb b/app/workers/archive_trace_worker.rb index 629526ec17c..ecde05f94dc 100644 --- a/app/workers/archive_trace_worker.rb +++ b/app/workers/archive_trace_worker.rb @@ -1,16 +1,5 @@ # frozen_string_literal: true -class ArchiveTraceWorker # rubocop:disable Scalability/IdempotentWorker - include ApplicationWorker - - sidekiq_options retry: 3 - include PipelineBackgroundQueue - - # rubocop: disable CodeReuse/ActiveRecord - def perform(job_id) - Ci::Build.without_archived_trace.find_by(id: job_id).try do |job| - Ci::ArchiveTraceService.new.execute(job, worker_name: self.class.name) - end - end - # rubocop: enable CodeReuse/ActiveRecord +class ArchiveTraceWorker < ::Ci::ArchiveTraceWorker # rubocop:disable Scalability/IdempotentWorker + # DEPRECATED: Not triggered since https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64934/ end diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index a3eaacec8a2..0d41f7b9438 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -1,61 +1,9 @@ # frozen_string_literal: true -class BuildFinishedWorker # rubocop:disable Scalability/IdempotentWorker - include ApplicationWorker +class BuildFinishedWorker < ::Ci::BuildFinishedWorker # rubocop:disable Scalability/IdempotentWorker + # DEPRECATED: Not triggered since https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64934/ - sidekiq_options retry: 3 - include PipelineQueue - - queue_namespace :pipeline_processing + # We need to explicitly specify these settings. They aren't inheriting from the parent class. urgency :high worker_resource_boundary :cpu - - ARCHIVE_TRACES_IN = 2.minutes.freeze - - # rubocop: disable CodeReuse/ActiveRecord - def perform(build_id) - Ci::Build.find_by(id: build_id).try do |build| - process_build(build) - end - end - # rubocop: enable CodeReuse/ActiveRecord - - private - - # Processes a single CI build that has finished. - # - # This logic resides in a separate method so that EE can extend it more - # easily. - # - # @param [Ci::Build] build The build to process. - def process_build(build) - # We execute these in sync to reduce IO. - build.parse_trace_sections! - build.update_coverage - Ci::BuildReportResultService.new.execute(build) - - # We execute these async as these are independent operations. - BuildHooksWorker.perform_async(build.id) - ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat? - - if build.failed? - ::Ci::MergeRequests::AddTodoWhenBuildFailsWorker.perform_async(build.id) - end - - ## - # We want to delay sending a build trace to object storage operation to - # validate that this fixes a race condition between this and flushing live - # trace chunks and chunks being removed after consolidation and putting - # them into object storage archive. - # - # TODO This is temporary fix we should improve later, after we validate - # that this is indeed the culprit. - # - # See https://gitlab.com/gitlab-org/gitlab/-/issues/267112 for more - # details. - # - ArchiveTraceWorker.perform_in(ARCHIVE_TRACES_IN, build.id) - end end - -BuildFinishedWorker.prepend_mod_with('BuildFinishedWorker') diff --git a/app/workers/ci/archive_trace_worker.rb b/app/workers/ci/archive_trace_worker.rb new file mode 100644 index 00000000000..16288faf370 --- /dev/null +++ b/app/workers/ci/archive_trace_worker.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Ci + class ArchiveTraceWorker # rubocop:disable Scalability/IdempotentWorker + include ApplicationWorker + + sidekiq_options retry: 3 + include PipelineBackgroundQueue + + # rubocop: disable CodeReuse/ActiveRecord + def perform(job_id) + Ci::Build.without_archived_trace.find_by(id: job_id).try do |job| + Ci::ArchiveTraceService.new.execute(job, worker_name: self.class.name) + end + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb index c748bc33ada..5fe3adf870f 100644 --- a/app/workers/ci/archive_traces_cron_worker.rb +++ b/app/workers/ci/archive_traces_cron_worker.rb @@ -12,7 +12,7 @@ module Ci # rubocop: disable CodeReuse/ActiveRecord def perform # Archive stale live traces which still resides in redis or database - # This could happen when ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL + # This could happen when Ci::ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL # More details in https://gitlab.com/gitlab-org/gitlab-foss/issues/36791 Ci::Build.with_stale_live_trace.find_each(batch_size: 100) do |build| Ci::ArchiveTraceService.new.execute(build, worker_name: self.class.name) diff --git a/app/workers/ci/build_finished_worker.rb b/app/workers/ci/build_finished_worker.rb new file mode 100644 index 00000000000..1d6e3b1fa3c --- /dev/null +++ b/app/workers/ci/build_finished_worker.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Ci + class BuildFinishedWorker # rubocop:disable Scalability/IdempotentWorker + include ApplicationWorker + + sidekiq_options retry: 3 + include PipelineQueue + + queue_namespace :pipeline_processing + urgency :high + worker_resource_boundary :cpu + + ARCHIVE_TRACES_IN = 2.minutes.freeze + + # rubocop: disable CodeReuse/ActiveRecord + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + process_build(build) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + private + + # Processes a single CI build that has finished. + # + # This logic resides in a separate method so that EE can extend it more + # easily. + # + # @param [Ci::Build] build The build to process. + def process_build(build) + # We execute these in sync to reduce IO. + build.parse_trace_sections! + build.update_coverage + Ci::BuildReportResultService.new.execute(build) + + # We execute these async as these are independent operations. + BuildHooksWorker.perform_async(build.id) + ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat? + + if build.failed? + ::Ci::MergeRequests::AddTodoWhenBuildFailsWorker.perform_async(build.id) + end + + ## + # We want to delay sending a build trace to object storage operation to + # validate that this fixes a race condition between this and flushing live + # trace chunks and chunks being removed after consolidation and putting + # them into object storage archive. + # + # TODO This is temporary fix we should improve later, after we validate + # that this is indeed the culprit. + # + # See https://gitlab.com/gitlab-org/gitlab/-/issues/267112 for more + # details. + # + archive_trace_worker_class(build).perform_in(ARCHIVE_TRACES_IN, build.id) + end + + def archive_trace_worker_class(build) + if Feature.enabled?(:ci_build_finished_worker_namespace_changed, build.project, default_enabled: :yaml) + Ci::ArchiveTraceWorker + else + ::ArchiveTraceWorker + end + end + end +end + +Ci::BuildFinishedWorker.prepend_mod_with('Ci::BuildFinishedWorker') diff --git a/app/workers/concerns/gitlab/github_import/object_importer.rb b/app/workers/concerns/gitlab/github_import/object_importer.rb index ab333d020fb..1eff53cea01 100644 --- a/app/workers/concerns/gitlab/github_import/object_importer.rb +++ b/app/workers/concerns/gitlab/github_import/object_importer.rb @@ -36,25 +36,13 @@ module Gitlab importer_class.new(object, project, client).execute - increment_counters(project) + Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :imported) info(project.id, message: 'importer finished') rescue StandardError => e error(project.id, e, hash) end - # Counters incremented: - # - global (prometheus): for metrics in Grafana - # - project (redis): used in FinishImportWorker to report number of objects imported - def increment_counters(project) - counter.increment - Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :imported) - end - - def counter - @counter ||= Gitlab::Metrics.counter(counter_name, counter_description) - end - def object_type raise NotImplementedError end @@ -70,16 +58,6 @@ module Gitlab raise NotImplementedError end - # Returns the name (as a Symbol) of the Prometheus counter. - def counter_name - raise NotImplementedError - end - - # Returns the description (as a String) of the Prometheus counter. - def counter_description - raise NotImplementedError - end - private attr_accessor :github_id diff --git a/app/workers/database/partition_management_worker.rb b/app/workers/database/partition_management_worker.rb new file mode 100644 index 00000000000..c9b1cd6d261 --- /dev/null +++ b/app/workers/database/partition_management_worker.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Database + class PartitionManagementWorker + include ApplicationWorker + + sidekiq_options retry: 3 + include CronjobQueue # rubocop:disable Scalability/CronWorkerContext + + feature_category :database + idempotent! + + def perform + Gitlab::Database::Partitioning::PartitionManager.new.sync_partitions + ensure + Gitlab::Database::Partitioning::PartitionMonitoring.new.report_metrics + end + end +end diff --git a/app/workers/gitlab/github_import/import_diff_note_worker.rb b/app/workers/gitlab/github_import/import_diff_note_worker.rb index 5ee5fcaacd6..85b7d6c76bd 100644 --- a/app/workers/gitlab/github_import/import_diff_note_worker.rb +++ b/app/workers/gitlab/github_import/import_diff_note_worker.rb @@ -16,14 +16,6 @@ module Gitlab def object_type :diff_note end - - def counter_name - :github_importer_imported_diff_notes - end - - def counter_description - 'The number of imported GitHub pull request review comments' - end end end end diff --git a/app/workers/gitlab/github_import/import_issue_worker.rb b/app/workers/gitlab/github_import/import_issue_worker.rb index a3921e86c84..8fdc0219ffd 100644 --- a/app/workers/gitlab/github_import/import_issue_worker.rb +++ b/app/workers/gitlab/github_import/import_issue_worker.rb @@ -16,14 +16,6 @@ module Gitlab def object_type :issue end - - def counter_name - :github_importer_imported_issues - end - - def counter_description - 'The number of imported GitHub issues' - end end end end diff --git a/app/workers/gitlab/github_import/import_lfs_object_worker.rb b/app/workers/gitlab/github_import/import_lfs_object_worker.rb index ea755fc9a37..2a95366bac7 100644 --- a/app/workers/gitlab/github_import/import_lfs_object_worker.rb +++ b/app/workers/gitlab/github_import/import_lfs_object_worker.rb @@ -16,14 +16,6 @@ module Gitlab def object_type :lfs_object end - - def counter_name - :github_importer_imported_lfs_objects - end - - def counter_description - 'The number of imported GitHub Lfs Objects' - end end end end diff --git a/app/workers/gitlab/github_import/import_note_worker.rb b/app/workers/gitlab/github_import/import_note_worker.rb index d612e0b2e5b..2125c953778 100644 --- a/app/workers/gitlab/github_import/import_note_worker.rb +++ b/app/workers/gitlab/github_import/import_note_worker.rb @@ -16,14 +16,6 @@ module Gitlab def object_type :note end - - def counter_name - :github_importer_imported_notes - end - - def counter_description - 'The number of imported GitHub comments' - end end end end diff --git a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb b/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb index 2db404cca5d..91dab3470d9 100644 --- a/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb +++ b/app/workers/gitlab/github_import/import_pull_request_merged_by_worker.rb @@ -18,14 +18,6 @@ module Gitlab def object_type :pull_request_merged_by end - - def counter_name - :github_importer_imported_pull_requests_merged_by - end - - def counter_description - 'The number of imported GitHub pull requests merged by' - end end end end diff --git a/app/workers/gitlab/github_import/import_pull_request_review_worker.rb b/app/workers/gitlab/github_import/import_pull_request_review_worker.rb index 7ea867ddb39..de10fe40589 100644 --- a/app/workers/gitlab/github_import/import_pull_request_review_worker.rb +++ b/app/workers/gitlab/github_import/import_pull_request_review_worker.rb @@ -18,14 +18,6 @@ module Gitlab def object_type :pull_request_review end - - def counter_name - :github_importer_imported_pull_request_reviews - end - - def counter_description - 'The number of imported GitHub pull request reviews' - end end end end diff --git a/app/workers/gitlab/github_import/import_pull_request_worker.rb b/app/workers/gitlab/github_import/import_pull_request_worker.rb index f1d01adb736..79938a157d7 100644 --- a/app/workers/gitlab/github_import/import_pull_request_worker.rb +++ b/app/workers/gitlab/github_import/import_pull_request_worker.rb @@ -16,14 +16,6 @@ module Gitlab def object_type :pull_request end - - def counter_name - :github_importer_imported_pull_requests - end - - def counter_description - 'The number of imported GitHub pull requests' - end end end end diff --git a/app/workers/partition_creation_worker.rb b/app/workers/partition_creation_worker.rb index 2b21741d6c2..bb4834ab2dd 100644 --- a/app/workers/partition_creation_worker.rb +++ b/app/workers/partition_creation_worker.rb @@ -10,8 +10,7 @@ class PartitionCreationWorker idempotent! def perform - Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions - ensure - Gitlab::Database::Partitioning::PartitionMonitoring.new.report_metrics + # This worker has been removed in favor of Database::PartitionManagementWorker + Database::PartitionManagementWorker.new.perform end end |