summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-17 18:07:58 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-17 18:07:58 +0000
commitc18d1c1bd2d0339ddcff4d320ee306fa03692986 (patch)
tree69ba5a0895df814d4bc86508634dd843413d79e5 /app
parent46d07ca5c2b729d6396723290a875a317b2845ee (diff)
downloadgitlab-ce-c18d1c1bd2d0339ddcff4d320ee306fa03692986.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci/runner/admin_register_runner/admin_register_runner_app.vue51
-rw-r--r--app/assets/javascripts/ci/runner/components/registration/registration_instructions.vue65
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_add_note.vue50
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_comment_form.vue68
-rw-r--r--app/assets/javascripts/work_items/components/notes/work_item_note.vue122
-rw-r--r--app/graphql/mutations/achievements/revoke.rb33
-rw-r--r--app/graphql/types/mutation_type.rb3
-rw-r--r--app/helpers/system_note_helper.rb2
-rw-r--r--app/models/achievements/user_achievement.rb4
-rw-r--r--app/models/system_note_metadata.rb3
-rw-r--r--app/services/achievements/revoke_service.rb47
-rw-r--r--app/services/ci/register_job_service.rb2
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">&middot;</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">&middot;</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