diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-17 18:07:58 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-17 18:07:58 +0000 |
commit | c18d1c1bd2d0339ddcff4d320ee306fa03692986 (patch) | |
tree | 69ba5a0895df814d4bc86508634dd843413d79e5 /app | |
parent | 46d07ca5c2b729d6396723290a875a317b2845ee (diff) | |
download | gitlab-ce-c18d1c1bd2d0339ddcff4d320ee306fa03692986.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
12 files changed, 270 insertions, 180 deletions
diff --git a/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue b/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue index f8d3a1df5dd..cd38dc07157 100644 --- a/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue +++ b/app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue @@ -1,20 +1,9 @@ <script> import { GlButton } from '@gitlab/ui'; -import { createAlert } from '~/alert'; import { getParameterByName, updateHistory, mergeUrlParams } from '~/lib/utils/url_utility'; -import { convertToGraphQLId } from '~/graphql_shared/utils'; -import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants'; -import runnerForRegistrationQuery from '../graphql/register/runner_for_registration.query.graphql'; -import { - I18N_FETCH_ERROR, - PARAM_KEY_PLATFORM, - DEFAULT_PLATFORM, - STATUS_ONLINE, - RUNNER_REGISTRATION_POLLING_INTERVAL_MS, -} from '../constants'; +import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM } from '../constants'; import RegistrationInstructions from '../components/registration/registration_instructions.vue'; import PlatformsDrawer from '../components/registration/platforms_drawer.vue'; -import { captureException } from '../sentry_utils'; export default { name: 'AdminRegisterRunnerApp', @@ -36,43 +25,9 @@ export default { data() { return { platform: getParameterByName(PARAM_KEY_PLATFORM) || DEFAULT_PLATFORM, - runner: null, - token: null, isDrawerOpen: false, }; }, - apollo: { - runner: { - query: runnerForRegistrationQuery, - variables() { - return { - id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId), - }; - }, - manual: true, - result({ data }) { - if (data?.runner) { - const { ephemeralAuthenticationToken, ...runner } = data.runner; - this.runner = runner; - - // The token is available in the API for a limited amount of time - // preserve its original value if it is missing after polling. - this.token = ephemeralAuthenticationToken || this.token; - } - }, - error(error) { - createAlert({ message: I18N_FETCH_ERROR }); - captureException({ error, component: this.$options.name }); - }, - pollInterval() { - if (this.runner?.status === STATUS_ONLINE) { - // stop polling - return 0; - } - return RUNNER_REGISTRATION_POLLING_INTERVAL_MS; - }, - }, - }, watch: { platform(platform) { updateHistory({ @@ -93,10 +48,8 @@ export default { <template> <div> <registration-instructions - :runner="runner" - :token="token" + :runner-id="runnerId" :platform="platform" - :loading="$apollo.queries.runner.loading" @toggleDrawer="onToggleDrawer" > <template #runner-list-name>{{ s__('Runners|Admin area › Runners') }}</template> diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue index 11661888c85..2f3c172666d 100644 --- a/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue +++ b/app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue @@ -1,18 +1,27 @@ <script> import { GlIcon, GlLink, GlSprintf, GlSkeletonLoader } from '@gitlab/ui'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { createAlert } from '~/alert'; import { s__, sprintf } from '~/locale'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants'; +import runnerForRegistrationQuery from '../../graphql/register/runner_for_registration.query.graphql'; import { + STATUS_ONLINE, EXECUTORS_HELP_URL, SERVICE_COMMANDS_HELP_URL, - STATUS_ONLINE, + RUNNER_REGISTRATION_POLLING_INTERVAL_MS, + I18N_FETCH_ERROR, I18N_REGISTRATION_SUCCESS, } from '../../constants'; +import { captureException } from '../../sentry_utils'; + import CliCommand from './cli_command.vue'; import { commandPrompt, registerCommand, runCommand } from './utils'; export default { + name: 'RegistrationInstructions', components: { GlIcon, GlLink, @@ -22,27 +31,57 @@ export default { CliCommand, }, props: { - runner: { - type: Object, - required: false, - default: null, - }, - token: { + runnerId: { type: String, - required: false, - default: null, + required: true, }, platform: { type: String, required: true, }, - loading: { - type: Boolean, - required: false, - default: false, + }, + data() { + return { + runner: null, + token: null, + }; + }, + apollo: { + runner: { + query: runnerForRegistrationQuery, + variables() { + return { + id: convertToGraphQLId(TYPENAME_CI_RUNNER, this.runnerId), + }; + }, + manual: true, + result({ data }) { + if (data?.runner) { + const { ephemeralAuthenticationToken, ...runner } = data.runner; + this.runner = runner; + + // The token is available in the API for a limited amount of time + // preserve its original value if it is missing after polling. + this.token = ephemeralAuthenticationToken || this.token; + } + }, + error(error) { + createAlert({ message: I18N_FETCH_ERROR }); + captureException({ error, component: this.$options.name }); + }, + pollInterval() { + if (this.runner?.status === STATUS_ONLINE) { + // stop polling + return 0; + } + return RUNNER_REGISTRATION_POLLING_INTERVAL_MS; + }, }, }, computed: { + loading() { + return this.$apollo.queries.runner.loading; + }, description() { return this.runner?.description; }, diff --git a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue index 8278c72076c..1762344ea9e 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_add_note.vue @@ -130,7 +130,19 @@ export default { timelineEntryInnerClass() { return { 'timeline-entry-inner': true, - 'gl-p-5': this.addPadding, + 'gl-pb-3': this.addPadding, + }; + }, + timelineContentClass() { + return { + 'timeline-content': true, + 'gl-border-0! gl-pl-0!': !this.addPadding, + }; + }, + parentClass() { + return { + 'gl-relative gl-display-flex gl-align-items-flex-start gl-flex-wrap-nowrap': !this + .isEditing, }; }, isProjectArchived() { @@ -200,23 +212,27 @@ export default { :is-project-archived="isProjectArchived" /> <div v-else :class="timelineEntryInnerClass"> - <div class="gl-relative gl-display-flex gl-align-items-flex-start gl-flex-wrap-nowrap"> + <div class="timeline-avatar gl-float-left"> <gl-avatar :src="$options.constantOptions.avatarUrl" :size="32" class="gl-mr-3" /> - <work-item-comment-form - v-if="isEditing" - :work-item-type="workItemType" - :aria-label="__('Add a reply')" - :is-submitting="isSubmitting" - :autosave-key="autosaveKey" - @submitForm="updateWorkItem" - @cancelEditing="cancelEditing" - /> - <gl-button - v-else - class="gl-flex-grow-1 gl-justify-content-start! gl-text-secondary!" - @click="isEditing = true" - >{{ __('Add a reply') }}</gl-button - > + </div> + <div :class="timelineContentClass"> + <div :class="parentClass"> + <work-item-comment-form + v-if="isEditing" + :work-item-type="workItemType" + :aria-label="__('Add a reply')" + :is-submitting="isSubmitting" + :autosave-key="autosaveKey" + @submitForm="updateWorkItem" + @cancelEditing="cancelEditing" + /> + <gl-button + v-else + class="gl-flex-grow-1 gl-justify-content-start! gl-text-secondary!" + @click="isEditing = true" + >{{ __('Add a reply') }}</gl-button + > + </div> </div> </div> </li> diff --git a/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue b/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue index 1396d19d679..a3ebd51f76d 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue @@ -96,41 +96,39 @@ export default { </script> <template> - <div class="timeline-content"> - <div class="timeline-discussion-body"> - <div class="note-body"> - <form class="common-note-form gfm-form js-main-target-form gl-flex-grow-1"> - <markdown-editor - :value="commentText" - :render-markdown-path="markdownPreviewPath" - :markdown-docs-path="$options.constantOptions.markdownDocsPath" - :form-field-props="formFieldProps" - data-testid="work-item-add-comment" - class="gl-mb-3" - autofocus - use-bottom-toolbar - @input="setCommentText" - @keydown.meta.enter="$emit('submitForm', commentText)" - @keydown.ctrl.enter="$emit('submitForm', commentText)" - @keydown.esc.stop="cancelEditing" - /> - <gl-button - category="primary" - variant="confirm" - data-testid="confirm-button" - :loading="isSubmitting" - @click="$emit('submitForm', commentText)" - >{{ commentButtonText }} - </gl-button> - <gl-button - data-testid="cancel-button" - category="primary" - class="gl-ml-3" - @click="cancelEditing" - >{{ __('Cancel') }} - </gl-button> - </form> - </div> + <div class="timeline-discussion-body"> + <div class="note-body"> + <form class="common-note-form gfm-form js-main-target-form gl-flex-grow-1"> + <markdown-editor + :value="commentText" + :render-markdown-path="markdownPreviewPath" + :markdown-docs-path="$options.constantOptions.markdownDocsPath" + :form-field-props="formFieldProps" + data-testid="work-item-add-comment" + class="gl-mb-3" + autofocus + use-bottom-toolbar + @input="setCommentText" + @keydown.meta.enter="$emit('submitForm', commentText)" + @keydown.ctrl.enter="$emit('submitForm', commentText)" + @keydown.esc.stop="cancelEditing" + /> + <gl-button + category="primary" + variant="confirm" + data-testid="confirm-button" + :loading="isSubmitting" + @click="$emit('submitForm', commentText)" + >{{ commentButtonText }} + </gl-button> + <gl-button + data-testid="cancel-button" + category="primary" + class="gl-ml-3" + @click="cancelEditing" + >{{ __('Cancel') }} + </gl-button> + </form> </div> </div> </template> diff --git a/app/assets/javascripts/work_items/components/notes/work_item_note.vue b/app/assets/javascripts/work_items/components/notes/work_item_note.vue index 0940d93a79e..dcb6557600e 100644 --- a/app/assets/javascripts/work_items/components/notes/work_item_note.vue +++ b/app/assets/javascripts/work_items/components/notes/work_item_note.vue @@ -171,71 +171,73 @@ export default { /> </gl-avatar-link> </div> - <work-item-comment-form - v-if="isEditing" - :work-item-type="workItemType" - :aria-label="__('Edit comment')" - :autosave-key="autosaveKey" - :initial-value="note.body" - :comment-button-text="__('Save comment')" - @cancelEditing="isEditing = false" - @submitForm="updateNote" - /> - <div v-else class="timeline-content" data-testid="note-wrapper"> - <div :class="noteHeaderClass"> - <note-header - :author="author" - :created-at="note.createdAt" - :note-id="note.id" - :note-url="note.url" - > - <span v-if="note.createdAt" class="d-none d-sm-inline">·</span> - </note-header> - <div class="gl-display-inline-flex"> - <note-actions - :show-award-emoji="hasAwardEmojiPermission" - :note-url="noteUrl" - :show-reply="showReply" - :show-edit="hasAdminPermission" + <div class="timeline-content"> + <work-item-comment-form + v-if="isEditing" + :work-item-type="workItemType" + :aria-label="__('Edit comment')" + :autosave-key="autosaveKey" + :initial-value="note.body" + :comment-button-text="__('Save comment')" + @cancelEditing="isEditing = false" + @submitForm="updateNote" + /> + <div v-else data-testid="note-wrapper"> + <div :class="noteHeaderClass"> + <note-header + :author="author" + :created-at="note.createdAt" :note-id="note.id" - @startReplying="showReplyForm" - @startEditing="startEditing" - @error="($event) => $emit('error', $event)" - /> - <gl-dropdown - v-gl-tooltip - icon="ellipsis_v" - text-sr-only - right - :text="$options.i18n.moreActionsText" - :title="$options.i18n.moreActionsText" - category="tertiary" - no-caret + :note-url="note.url" > - <gl-dropdown-item :data-clipboard-text="noteUrl" @click="notifyCopyDone"> - <span>{{ $options.i18n.copyLinkText }}</span> - </gl-dropdown-item> - <gl-dropdown-item - v-if="hasAdminPermission" - variant="danger" - data-testid="delete-note-action" - @click="$emit('deleteNote')" + <span v-if="note.createdAt" class="d-none d-sm-inline">·</span> + </note-header> + <div class="gl-display-inline-flex"> + <note-actions + :show-award-emoji="hasAwardEmojiPermission" + :note-url="noteUrl" + :show-reply="showReply" + :show-edit="hasAdminPermission" + :note-id="note.id" + @startReplying="showReplyForm" + @startEditing="startEditing" + @error="($event) => $emit('error', $event)" + /> + <gl-dropdown + v-gl-tooltip + icon="ellipsis_v" + text-sr-only + right + :text="$options.i18n.moreActionsText" + :title="$options.i18n.moreActionsText" + category="tertiary" + no-caret > - {{ $options.i18n.deleteNoteText }} - </gl-dropdown-item> - </gl-dropdown> + <gl-dropdown-item :data-clipboard-text="noteUrl" @click="notifyCopyDone"> + <span>{{ $options.i18n.copyLinkText }}</span> + </gl-dropdown-item> + <gl-dropdown-item + v-if="hasAdminPermission" + variant="danger" + data-testid="delete-note-action" + @click="$emit('deleteNote')" + > + {{ $options.i18n.deleteNoteText }} + </gl-dropdown-item> + </gl-dropdown> + </div> </div> + <div class="timeline-discussion-body"> + <note-body ref="noteBody" :note="note" :has-replies="hasReplies" /> + </div> + <edited-at + v-if="note.lastEditedBy" + :updated-at="note.lastEditedAt" + :updated-by-name="lastEditedBy.name" + :updated-by-path="lastEditedBy.webPath" + :class="isFirstNote ? 'gl-pl-3' : 'gl-pl-8'" + /> </div> - <div class="timeline-discussion-body"> - <note-body ref="noteBody" :note="note" :has-replies="hasReplies" /> - </div> - <edited-at - v-if="note.lastEditedBy" - :updated-at="note.lastEditedAt" - :updated-by-name="lastEditedBy.name" - :updated-by-path="lastEditedBy.webPath" - :class="isFirstNote ? 'gl-pl-3' : 'gl-pl-8'" - /> </div> </timeline-entry-item> </template> diff --git a/app/graphql/mutations/achievements/revoke.rb b/app/graphql/mutations/achievements/revoke.rb new file mode 100644 index 00000000000..9d21b1c3741 --- /dev/null +++ b/app/graphql/mutations/achievements/revoke.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Mutations + module Achievements + class Revoke < BaseMutation + graphql_name 'AchievementsRevoke' + + include Gitlab::Graphql::Authorize::AuthorizeResource + + field :user_achievement, + ::Types::Achievements::UserAchievementType, + null: true, + description: 'Achievement award.' + + argument :user_achievement_id, ::Types::GlobalIDType[::Achievements::UserAchievement], + required: true, + description: 'Global ID of the user achievement being revoked.' + + authorize :award_achievement + + def resolve(args) + user_achievement = authorized_find!(id: args[:user_achievement_id]) + + result = ::Achievements::RevokeService.new(current_user, user_achievement).execute + { user_achievement: result.payload, errors: result.errors } + end + + def find_object(id:) + GitlabSchema.object_from_id(id, expected_type: ::Achievements::UserAchievement) + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index c5f574c0ce7..9bdbdad4386 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -7,7 +7,8 @@ module Types include Gitlab::Graphql::MountMutation mount_mutation Mutations::Achievements::Award, alpha: { milestone: '15.10' } - mount_mutation Mutations::Achievements::Create + mount_mutation Mutations::Achievements::Create, alpha: { milestone: '15.8' } + mount_mutation Mutations::Achievements::Revoke, alpha: { milestone: '15.10' } mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs mount_mutation Mutations::AlertManagement::CreateAlertIssue mount_mutation Mutations::AlertManagement::UpdateAlertStatus diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 3e5f63796b2..a1b6e896475 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -42,8 +42,6 @@ module SystemNoteHelper 'severity' => 'information-o', 'cloned' => 'documents', 'issue_type' => 'pencil', - 'attention_requested' => 'user', - 'attention_request_removed' => 'user', 'contact' => 'users', 'timeline_event' => 'clock', 'relate_to_child' => 'link', diff --git a/app/models/achievements/user_achievement.rb b/app/models/achievements/user_achievement.rb index 293dad7aa24..e3f810f8846 100644 --- a/app/models/achievements/user_achievement.rb +++ b/app/models/achievements/user_achievement.rb @@ -13,5 +13,9 @@ module Achievements class_name: 'User', inverse_of: :revoked_user_achievements, optional: true + + def revoked? + revoked_by_user_id.present? + end end end diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index bb8527d8c01..0e0534d45ae 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -26,8 +26,7 @@ class SystemNoteMetadata < ApplicationRecord title time_tracking branch milestone discussion task moved cloned opened closed merged duplicate locked unlocked outdated reviewer tag due_date start_date_or_due_date pinned_embed cherry_pick health_status approved unapproved - status alert_issue_added relate unrelate new_alert_added severity - attention_requested attention_request_removed contact timeline_event + status alert_issue_added relate unrelate new_alert_added severity contact timeline_event issue_type relate_to_child unrelate_from_child relate_to_parent unrelate_from_parent ].freeze diff --git a/app/services/achievements/revoke_service.rb b/app/services/achievements/revoke_service.rb new file mode 100644 index 00000000000..4601622f517 --- /dev/null +++ b/app/services/achievements/revoke_service.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Achievements + class RevokeService + attr_reader :current_user, :user_achievement + + def initialize(current_user, user_achievement) + @current_user = current_user + @user_achievement = user_achievement + end + + def execute + return error_no_permissions unless allowed?(user_achievement.achievement) + return error_already_revoked if user_achievement.revoked? + + user_achievement.assign_attributes({ + revoked_by_user_id: current_user.id, + revoked_at: Time.zone.now + }) + return error_awarding unless user_achievement.save + + ServiceResponse.success(payload: user_achievement) + end + + private + + def allowed?(achievement) + current_user&.can?(:award_achievement, achievement) + end + + def error_no_permissions + error('You have insufficient permissions to revoke this achievement') + end + + def error_already_revoked + error('This achievement has already been revoked') + end + + def error_awarding + error(user_achievement&.errors&.full_messages || 'Failed to revoke achievement') + end + + def error(message) + ServiceResponse.error(message: Array(message)) + end + end +end diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index 8db96ea47c2..228a246f480 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -244,7 +244,6 @@ module Ci def assign_runner!(build, params) build.runner_id = runner.id build.runner_session_attributes = params[:session] if params[:session].present? - build.runner_machine = runner_machine if runner_machine failure_reason, _ = pre_assign_runner_checks.find { |_, check| check.call(build, params) } @@ -256,6 +255,7 @@ module Ci @metrics.increment_queue_operation(:runner_pre_assign_checks_success) build.run! + build.runner_machine = runner_machine if runner_machine end !failure_reason |