diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-11 00:08:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-11 00:08:21 +0300 |
commit | e43574ee831197b604c59b80f94e30764223e5ed (patch) | |
tree | f467f4c23cd9d151b5d54711451252de94d62879 /app | |
parent | d18b7dc5eea84db5008986c6879a24ad7f6462a6 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
19 files changed, 282 insertions, 122 deletions
diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue index 69205d7d250..bafbe94b8bd 100644 --- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue +++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue @@ -11,6 +11,7 @@ import { GlFormInput, } from '@gitlab/ui'; import { sprintf } from '~/locale'; +import ContentTransition from '~/vue_shared/components/content_transition.vue'; import { ACCESS_LEVEL, ACCESS_EXPIRE_DATE, @@ -20,6 +21,17 @@ import { HEADER_CLOSE_LABEL, } from '../constants'; +const DEFAULT_SLOT = 'default'; +const DEFAULT_SLOTS = [ + { + key: DEFAULT_SLOT, + attributes: { + class: 'invite-modal-content', + 'data-testid': 'invite-modal-initial-content', + }, + }, +]; + export default { components: { GlFormGroup, @@ -31,6 +43,7 @@ export default { GlSprintf, GlButton, GlFormInput, + ContentTransition, }, inheritAttrs: false, props: { @@ -86,6 +99,21 @@ export default { required: false, default: '', }, + submitButtonText: { + type: String, + required: false, + default: INVITE_BUTTON_TEXT, + }, + currentSlot: { + type: String, + required: false, + default: DEFAULT_SLOT, + }, + extraSlots: { + type: Array, + required: false, + default: () => [], + }, }, data() { // Be sure to check out reset! @@ -110,6 +138,9 @@ export default { (key) => this.accessLevels[key] === Number(this.selectedAccessLevel), ); }, + contentSlots() { + return [...DEFAULT_SLOTS, ...(this.extraSlots || [])]; + }, }, watch: { selectedAccessLevel: { @@ -148,6 +179,7 @@ export default { READ_MORE_TEXT, INVITE_BUTTON_TEXT, CANCEL_BUTTON_TEXT, + DEFAULT_SLOT, }; </script> @@ -164,79 +196,96 @@ export default { @close="reset" @hide="reset" > - <div class="gl-display-flex" data-testid="modal-base-intro-text"> - <slot name="intro-text-before"></slot> - <p> - <gl-sprintf :message="introText"> - <template #strong="{ content }"> - <strong>{{ content }}</strong> - </template> - </gl-sprintf> - </p> - <slot name="intro-text-after"></slot> - </div> - - <gl-form-group - :invalid-feedback="invalidFeedbackMessage" - :state="validationState" - :description="formGroupDescription" - data-testid="members-form-group" + <content-transition + class="gl-display-grid" + transition-name="invite-modal-transition" + :slots="contentSlots" + :current-slot="currentSlot" > - <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label> - <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot> - </gl-form-group> + <template #[$options.DEFAULT_SLOT]> + <div class="gl-display-flex" data-testid="modal-base-intro-text"> + <slot name="intro-text-before"></slot> + <p> + <gl-sprintf :message="introText"> + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + </gl-sprintf> + </p> + <slot name="intro-text-after"></slot> + </div> - <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label> - <div class="gl-mt-2 gl-w-half gl-xs-w-full"> - <gl-dropdown - class="gl-shadow-none gl-w-full" - data-qa-selector="access_level_dropdown" - v-bind="$attrs" - :text="selectedRoleName" - > - <template v-for="(key, item) in accessLevels"> - <gl-dropdown-item - :key="key" - active-class="is-active" - is-check-item - :is-checked="key === selectedAccessLevel" - @click="changeSelectedItem(key)" - > - <div>{{ item }}</div> - </gl-dropdown-item> - </template> - </gl-dropdown> - </div> + <gl-form-group + :invalid-feedback="invalidFeedbackMessage" + :state="validationState" + :description="formGroupDescription" + data-testid="members-form-group" + > + <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label> + <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot> + </gl-form-group> - <div class="gl-mt-2 gl-w-half gl-xs-w-full"> - <gl-sprintf :message="$options.READ_MORE_TEXT"> - <template #link="{ content }"> - <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </div> + <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label> + <div class="gl-mt-2 gl-w-half gl-xs-w-full"> + <gl-dropdown + class="gl-shadow-none gl-w-full" + data-qa-selector="access_level_dropdown" + v-bind="$attrs" + :text="selectedRoleName" + > + <template v-for="(key, item) in accessLevels"> + <gl-dropdown-item + :key="key" + active-class="is-active" + is-check-item + :is-checked="key === selectedAccessLevel" + @click="changeSelectedItem(key)" + > + <div>{{ item }}</div> + </gl-dropdown-item> + </template> + </gl-dropdown> + </div> - <label class="gl-mt-5 gl-display-block" for="expires_at">{{ - $options.ACCESS_EXPIRE_DATE - }}</label> - <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block"> - <gl-datepicker - v-model="selectedDate" - class="gl-display-inline!" - :min-date="minDate" - :target="null" - > - <template #default="{ formattedDate }"> - <gl-form-input class="gl-w-full" :value="formattedDate" :placeholder="__(`YYYY-MM-DD`)" /> - </template> - </gl-datepicker> - </div> - <slot name="form-after"></slot> + <div class="gl-mt-2 gl-w-half gl-xs-w-full"> + <gl-sprintf :message="$options.READ_MORE_TEXT"> + <template #link="{ content }"> + <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </div> + <label class="gl-mt-5 gl-display-block" for="expires_at">{{ + $options.ACCESS_EXPIRE_DATE + }}</label> + <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block"> + <gl-datepicker + v-model="selectedDate" + class="gl-display-inline!" + :min-date="minDate" + :target="null" + > + <template #default="{ formattedDate }"> + <gl-form-input + class="gl-w-full" + :value="formattedDate" + :placeholder="__(`YYYY-MM-DD`)" + /> + </template> + </gl-datepicker> + </div> + <slot name="form-after"></slot> + </template> + <template v-for="{ key } in extraSlots" #[key]> + <slot :name="key"></slot> + </template> + </content-transition> <template #modal-footer> - <gl-button data-testid="cancel-button" @click="closeModal"> - {{ $options.CANCEL_BUTTON_TEXT }} - </gl-button> + <slot name="cancel-button"> + <gl-button data-testid="cancel-button" @click="closeModal"> + {{ $options.CANCEL_BUTTON_TEXT }} + </gl-button> + </slot> <gl-button :disabled="submitDisabled" :loading="isLoading" @@ -245,7 +294,7 @@ export default { data-testid="invite-button" @click="submit" > - {{ $options.INVITE_BUTTON_TEXT }} + {{ submitButtonText }} </gl-button> </template> </gl-modal> diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js index c26abc261ed..173eef0646b 100644 --- a/app/assets/javascripts/tracking/tracking.js +++ b/app/assets/javascripts/tracking/tracking.js @@ -10,6 +10,8 @@ import { addReferrersCacheEntry, } from './utils'; +const ALLOWED_URL_HASHES = ['#diff', '#note']; + export default class Tracking { static queuedEvents = []; static initialized = false; @@ -183,7 +185,9 @@ export default class Tracking { originalUrl: window.location.href, }); - window.snowplow('setCustomUrl', pageLinks.url); + const appendHash = ALLOWED_URL_HASHES.some((prefix) => window.location.hash.startsWith(prefix)); + const customUrl = `${pageUrl}${appendHash ? window.location.hash : ''}`; + window.snowplow('setCustomUrl', customUrl); if (document.referrer) { const node = referrers.find((links) => links.originalUrl === document.referrer); diff --git a/app/assets/javascripts/vue_shared/components/content_transition.vue b/app/assets/javascripts/vue_shared/components/content_transition.vue new file mode 100644 index 00000000000..446610d6b91 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/content_transition.vue @@ -0,0 +1,32 @@ +<script> +export default { + props: { + currentSlot: { + type: String, + required: true, + }, + slots: { + type: Array, + required: true, + }, + transitionName: { + type: String, + required: true, + }, + }, + methods: { + shouldShow(key) { + return this.currentSlot === key; + }, + }, +}; +</script> +<template> + <div> + <transition v-for="{ key, attributes } in slots" :key="key" :name="transitionName"> + <div v-show="shouldShow(key)" v-bind="attributes"> + <slot :name="key"></slot> + </div> + </transition> + </div> +</template> diff --git a/app/controllers/groups/harbor/repositories_controller.rb b/app/controllers/groups/harbor/repositories_controller.rb new file mode 100644 index 00000000000..364607f9b20 --- /dev/null +++ b/app/controllers/groups/harbor/repositories_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Groups + module Harbor + class RepositoriesController < Groups::ApplicationController + feature_category :integrations + + before_action :harbor_registry_enabled! + before_action do + push_frontend_feature_flag(:harbor_registry_integration) + end + + def show + render :index + end + + private + + def harbor_registry_enabled! + render_404 unless Feature.enabled?(:harbor_registry_integration) + end + end + end +end diff --git a/app/controllers/projects/harbor/application_controller.rb b/app/controllers/projects/harbor/application_controller.rb new file mode 100644 index 00000000000..e6e694783fa --- /dev/null +++ b/app/controllers/projects/harbor/application_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Projects + module Harbor + class ApplicationController < Projects::ApplicationController + layout 'project' + + before_action :harbor_registry_enabled! + before_action do + push_frontend_feature_flag(:harbor_registry_integration) + end + + feature_category :integrations + + private + + def harbor_registry_enabled! + render_404 unless Feature.enabled?(:harbor_registry_integration) + end + end + end +end diff --git a/app/controllers/projects/harbor/repositories_controller.rb b/app/controllers/projects/harbor/repositories_controller.rb new file mode 100644 index 00000000000..dd3e3dc1978 --- /dev/null +++ b/app/controllers/projects/harbor/repositories_controller.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Projects + module Harbor + class RepositoriesController < ::Projects::Harbor::ApplicationController + def show + render :index + end + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index e49a39079ef..fa58455ad35 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -391,7 +391,7 @@ class User < ApplicationRecord # rubocop: disable CodeReuse/ServiceClass # Ideally we should not call a service object here but user.block - # is also bcalled by Users::MigrateToGhostUserService which references + # is also called by Users::MigrateToGhostUserService which references # this state transition object in order to do a rollback. # For this reason the tradeoff is to disable this cop. after_transition any => :blocked do |user| diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb index 015dfc16df0..082993130a1 100644 --- a/app/presenters/ci/build_runner_presenter.rb +++ b/app/presenters/ci/build_runner_presenter.rb @@ -64,50 +64,35 @@ module Ci def create_archive(artifacts) return unless artifacts[:untracked] || artifacts[:paths] - BuildArtifact.for_archive(artifacts).to_h.tap do |artifact| - artifact.delete(:exclude) unless artifact[:exclude].present? + archive = { + artifact_type: :archive, + artifact_format: :zip, + name: artifacts[:name], + untracked: artifacts[:untracked], + paths: artifacts[:paths], + when: artifacts[:when], + expire_in: artifacts[:expire_in] + } + + if artifacts.dig(:exclude).present? + archive.merge(exclude: artifacts[:exclude]) + else + archive end end def create_reports(reports, expire_in:) return unless reports&.any? - reports.map { |report| BuildArtifact.for_report(report, expire_in).to_h.compact } - end - - BuildArtifact = Struct.new(:name, :untracked, :paths, :exclude, :when, :expire_in, :artifact_type, :artifact_format, keyword_init: true) do - def self.for_archive(artifacts) - self.new( - artifact_type: :archive, - artifact_format: :zip, - name: artifacts[:name], - untracked: artifacts[:untracked], - paths: artifacts[:paths], - when: artifacts[:when], - expire_in: artifacts[:expire_in], - exclude: artifacts[:exclude] - ) - end - - def self.for_report(report, expire_in) - type, params = report - - if type == :coverage_report - artifact_type = params[:coverage_format].to_sym - paths = [params[:path]] - else - artifact_type = type - paths = params - end - - self.new( - artifact_type: artifact_type, - artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(artifact_type), - name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(artifact_type), - paths: paths, + reports.map do |report_type, report_paths| + { + artifact_type: report_type.to_sym, + artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym), + name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym), + paths: report_paths, when: 'always', expire_in: expire_in - ) + } end end diff --git a/app/services/ci/runners/unassign_runner_service.rb b/app/services/ci/runners/unassign_runner_service.rb index a38a4196021..1e46cf6add8 100644 --- a/app/services/ci/runners/unassign_runner_service.rb +++ b/app/services/ci/runners/unassign_runner_service.rb @@ -6,8 +6,9 @@ module Ci # @param [Ci::RunnerProject] runner_project the runner/project association to destroy # @param [User] user the user performing the operation def initialize(runner_project, user) - @runner = runner_project.runner @runner_project = runner_project + @runner = runner_project.runner + @project = runner_project.project @user = user end @@ -16,6 +17,12 @@ module Ci @runner_project.destroy end + + private + + attr_reader :runner, :project, :user end end end + +Ci::Runners::UnassignRunnerService.prepend_mod diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 4ec875098fa..46eec082125 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -54,7 +54,7 @@ module Users yield(user) if block_given? - MigrateToGhostUserService.new(user).execute unless options[:hard_delete] + MigrateToGhostUserService.new(user).execute(hard_delete: options[:hard_delete]) response = Snippets::BulkDestroyService.new(current_user, user.snippets).execute(options) raise DestroyError, response.message if response.error? diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb index 515d7821416..575614e8743 100644 --- a/app/services/users/migrate_to_ghost_user_service.rb +++ b/app/services/users/migrate_to_ghost_user_service.rb @@ -10,14 +10,21 @@ module Users class MigrateToGhostUserService extend ActiveSupport::Concern - attr_reader :ghost_user, :user + attr_reader :ghost_user, :user, :hard_delete def initialize(user) @user = user @ghost_user = User.ghost end - def execute + # If an admin attempts to hard delete a user, in some cases associated + # records may have a NOT NULL constraint on the user ID that prevent that record + # from being destroyed. In such situations we must assign the record to the ghost user. + # Passing in `hard_delete: true` will ensure these records get assigned to + # the ghost user before the user is destroyed. Other associated records will be destroyed. + # letting the other associated records be destroyed. + def execute(hard_delete: false) + @hard_delete = hard_delete transition = user.block_transition # Block the user before moving records to prevent a data race. @@ -46,6 +53,8 @@ module Users private def migrate_records + return if hard_delete + migrate_issues migrate_merge_requests migrate_notes diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 2c6c721a51c..766900b7a99 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -50,12 +50,12 @@ .todo-actions.gl-ml-3 - if todo.pending? = link_to dashboard_todo_path(todo), method: :delete, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-done-todo', data: { href: dashboard_todo_path(todo) } do + = gl_loading_icon(inline: true, css_class: 'gl-mr-2') Done - %span.gl-spinner.ml-1 = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-undo-todo hidden', data: { href: restore_dashboard_todo_path(todo) } do + = gl_loading_icon(inline: true, css_class: 'gl-mr-2') Undo - %span.gl-spinner.ml-1 - else = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-add-todo', data: { href: restore_dashboard_todo_path(todo) } do + = gl_loading_icon(inline: true, css_class: 'gl-mr-2') Add a to do - %span.gl-spinner.ml-1 diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index f6dc62e1d44..7a329fb34a9 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -22,11 +22,11 @@ - if @allowed_todos.any?(&:pending?) .gl-mr-3 = link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do + = gl_loading_icon(inline: true, css_class: 'gl-mr-2') = s_("Todos|Mark all as done") - %span.gl-spinner.ml-1 = link_to bulk_restore_dashboard_todos_path, class: 'gl-button btn btn-default btn-loading align-items-center js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do + = gl_loading_icon(inline: true, css_class: 'gl-mr-2') = s_("Todos|Undo mark all as done") - %span.gl-spinner.ml-1 .todos-filters .issues-details-filters.row-content-block.second-block diff --git a/app/views/groups/harbor/repositories/index.html.haml b/app/views/groups/harbor/repositories/index.html.haml new file mode 100644 index 00000000000..1ee15557e21 --- /dev/null +++ b/app/views/groups/harbor/repositories/index.html.haml @@ -0,0 +1,9 @@ +- page_title _("Harbor Registry") +- @content_class = "limit-container-width" unless fluid_layout + +#js-harbor-registry-list-group{ data: { endpoint: group_harbor_registries_path(@group), + "no_containers_image" => image_path('illustrations/docker-empty-state.svg'), + "containers_error_image" => image_path('illustrations/docker-error-state.svg'), + "help_page_path" => help_page_path('user/packages/container_registry/index'), + connection_error: (!!@connection_error).to_s, + invalid_path_error: (!!@invalid_path_error).to_s, } } diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 41333c416de..c9303e19d5d 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -45,5 +45,4 @@ - if local_assigns[:path] .js-edit-mode-pane#preview.hide .center - %h2 - %i.icon-spinner.icon-spin + = gl_loading_icon(size: 'lg') diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 6d2751bb7d4..1d3bec1ad44 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -20,8 +20,8 @@ = render 'shared/new_commit_form', placeholder: placeholder, ref: local_assigns[:ref] .form-actions - = button_tag class: 'btn gl-button btn-confirm btn-upload-file', id: 'submit-all', type: 'button' do - .gl-spinner.gl-mr-2.js-loading-icon.hidden + = button_tag class: 'btn gl-button btn-confirm btn-upload-file gl-mr-2', id: 'submit-all', type: 'button' do + = gl_loading_icon(inline: true, css_class: 'gl-mr-2 js-loading-icon hidden') = button_title = link_to _("Cancel"), '#', class: "btn gl-button btn-default btn-cancel", "data-dismiss" => "modal" diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 36d3520cb59..a3343aa4228 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -37,13 +37,13 @@ - @commit.parents.each do |parent| = link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha" .commit-info.branches - .gl-spinner.vertical-align-middle + = gl_loading_icon(inline: true, css_class: 'gl-vertical-align-middle') .well-segment.merge-request-info .icon-container = custom_icon('mr_bold') %span.commit-info.merge-requests{ 'data-project-commit-path' => merge_requests_project_commit_path(@project, @commit.id, format: :json) } - .gl-spinner.vertical-align-middle + = gl_loading_icon(inline: true, css_class: 'gl-vertical-align-middle') - if can?(current_user, :read_pipeline, @last_pipeline) .well-segment.pipeline-info diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f32514141c5..0d1b838906c 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -107,6 +107,6 @@ .save-project-loader.hide .center %h2 - .gl-spinner.gl-spinner-md.align-text-bottom + = gl_loading_icon(inline: true, size: 'md', css_class: 'gl-vertical-align-middle') = _('Saving project.') %p= _('Please wait a moment, this page will automatically refresh when ready.') diff --git a/app/views/projects/harbor/repositories/index.html.haml b/app/views/projects/harbor/repositories/index.html.haml new file mode 100644 index 00000000000..b3f5b91596d --- /dev/null +++ b/app/views/projects/harbor/repositories/index.html.haml @@ -0,0 +1,9 @@ +- page_title _("Harbor Registry") +- @content_class = "limit-container-width" unless fluid_layout + +#js-harbor-registry-list-project{ data: { endpoint: project_harbor_registry_index_path(@project), + "no_containers_image" => image_path('illustrations/docker-empty-state.svg'), + "containers_error_image" => image_path('illustrations/docker-error-state.svg'), + "help_page_path" => help_page_path('user/packages/container_registry/index'), + connection_error: (!!@connection_error).to_s, + invalid_path_error: (!!@invalid_path_error).to_s, } } |