Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-09-21 12:10:54 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-21 12:10:54 +0300
commit7e29b8f3fcc32a6adcf735a5a9069de5d9d814d6 (patch)
treedb9cd61369d239bf092628a8796395374f0c4468 /app
parentfccfc5332f11e87433ada819c2467713f2dbb8f3 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/issues/list/queries/issue.fragment.graphql1
-rw-r--r--app/assets/javascripts/issues/show/components/app.vue1
-rw-r--r--app/assets/javascripts/issues/show/components/sticky_header.vue9
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue42
-rw-r--r--app/graphql/mutations/achievements/update_user_achievement_priorities.rb37
-rw-r--r--app/graphql/resolvers/achievements/user_achievements_for_user_resolver.rb13
-rw-r--r--app/graphql/types/achievements/user_achievement_type.rb5
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/graphql/types/user_interface.rb2
-rw-r--r--app/models/achievements/user_achievement.rb17
-rw-r--r--app/policies/achievements/user_achievement_policy.rb11
-rw-r--r--app/services/achievements/update_user_achievement_priorities_service.rb44
12 files changed, 141 insertions, 42 deletions
diff --git a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
index f3173f0e33a..3b49c0efb14 100644
--- a/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
@@ -11,7 +11,6 @@ fragment IssueFragment on Issue {
moved
state
title
- titleHtml
updatedAt
closedAt
upvotes
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue
index d59692d2a28..d31b56c0277 100644
--- a/app/assets/javascripts/issues/show/components/app.vue
+++ b/app/assets/javascripts/issues/show/components/app.vue
@@ -550,7 +550,6 @@ export default {
:issuable-type="issuableType"
:show="isStickyHeaderShowing"
:title="state.titleText"
- :title-html="state.titleHtml"
@hide="hideStickyHeader"
@show="showStickyHeader"
/>
diff --git a/app/assets/javascripts/issues/show/components/sticky_header.vue b/app/assets/javascripts/issues/show/components/sticky_header.vue
index bcf10ee92bb..b8e0937d51c 100644
--- a/app/assets/javascripts/issues/show/components/sticky_header.vue
+++ b/app/assets/javascripts/issues/show/components/sticky_header.vue
@@ -6,7 +6,6 @@ import {
TYPE_EPIC,
WORKSPACE_PROJECT,
} from '~/issues/constants';
-import SafeHtml from '~/vue_shared/directives/safe_html';
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
export default {
@@ -19,7 +18,6 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
- SafeHtml,
},
props: {
isConfidential: {
@@ -54,10 +52,6 @@ export default {
type: String,
required: true,
},
- titleHtml: {
- type: String,
- required: true,
- },
},
computed: {
isClosed() {
@@ -118,10 +112,11 @@ export default {
<gl-icon name="spam" />
</span>
<a
- v-safe-html="titleHtml || title"
href="#top"
class="gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0 gl-text-black-normal"
+ :title="title"
>
+ {{ title }}
</a>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
index 690d9523a63..d9e750b9c45 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
@@ -8,7 +8,6 @@ import { isExternal, setUrlFragment } from '~/lib/utils/url_utility';
import { __, n__, sprintf } from '~/locale';
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-import SafeHtml from '~/vue_shared/directives/safe_html';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import { STATE_CLOSED } from '~/work_items/constants';
import { isAssigneesWidget, isLabelsWidget } from '~/work_items/utils';
@@ -25,7 +24,6 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
- SafeHtml,
},
mixins: [timeagoMixin],
props: {
@@ -91,9 +89,6 @@ export default {
authorId() {
return getIdFromGraphQLId(this.author.id);
},
- isIssueTrackerExternal() {
- return Boolean(this.issuable.externalTracker);
- },
isIssuableUrlExternal() {
return isExternal(this.webUrl ?? '');
},
@@ -269,33 +264,18 @@ export default {
:title="__('This issue is hidden because its author has been banned')"
:aria-label="__('Hidden')"
/>
- <template v-if="isIssueTrackerExternal">
- <gl-link
- class="issue-title-text"
- dir="auto"
- :href="webUrl"
- data-qa-selector="issuable_title_link"
- data-testid="issuable-title-link"
- v-bind="issuableTitleProps"
- @click="handleIssuableItemClick"
- >
- {{ issuable.title }}
- <gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" />
- </gl-link>
- </template>
- <template v-else>
- <gl-link
- v-safe-html="issuable.titleHtml || issuable.title"
- class="issue-title-text"
- dir="auto"
- :href="webUrl"
- data-qa-selector="issuable_title_link"
- data-testid="issuable-title-link"
- v-bind="issuableTitleProps"
- @click="handleIssuableItemClick"
- />
+ <gl-link
+ class="issue-title-text"
+ dir="auto"
+ :href="webUrl"
+ data-qa-selector="issuable_title_link"
+ data-testid="issuable-title-link"
+ v-bind="issuableTitleProps"
+ @click="handleIssuableItemClick"
+ >
+ {{ issuable.title }}
<gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" />
- </template>
+ </gl-link>
<span
v-if="taskStatus"
class="task-status gl-display-none gl-sm-display-inline-block! gl-ml-2 gl-font-sm"
diff --git a/app/graphql/mutations/achievements/update_user_achievement_priorities.rb b/app/graphql/mutations/achievements/update_user_achievement_priorities.rb
new file mode 100644
index 00000000000..077b4810fdc
--- /dev/null
+++ b/app/graphql/mutations/achievements/update_user_achievement_priorities.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Achievements
+ class UpdateUserAchievementPriorities < BaseMutation
+ graphql_name 'UserAchievementPrioritiesUpdate'
+
+ field :user_achievements,
+ [::Types::Achievements::UserAchievementType],
+ null: false,
+ description: 'Updated user achievements.'
+
+ argument :user_achievement_ids,
+ [::Types::GlobalIDType[::Achievements::UserAchievement]],
+ required: true,
+ description: 'Global IDs of the user achievements being prioritized, ' \
+ 'ordered from highest to lowest priority.'
+
+ def resolve(args)
+ user_achievements = args.delete(:user_achievement_ids).map { |id| find_object(id) }
+
+ user_achievements.each do |user_achievement|
+ unless Ability.allowed?(current_user, :update_owned_user_achievement, user_achievement)
+ raise_resource_not_available_error!
+ end
+ end
+
+ result = ::Achievements::UpdateUserAchievementPrioritiesService.new(current_user, user_achievements).execute
+ { user_achievements: result.payload, errors: result.errors }
+ end
+
+ def find_object(id)
+ ::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(id, expected_type: ::Achievements::UserAchievement))
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/achievements/user_achievements_for_user_resolver.rb b/app/graphql/resolvers/achievements/user_achievements_for_user_resolver.rb
new file mode 100644
index 00000000000..673babcf14a
--- /dev/null
+++ b/app/graphql/resolvers/achievements/user_achievements_for_user_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Achievements
+ # rubocop:disable Graphql/ResolverType -- the type is inherited from the parent class
+ class UserAchievementsForUserResolver < UserAchievementsResolver
+ def resolve_with_lookahead
+ super.order_by_priority_asc
+ end
+ end
+ # rubocop:enable Graphql/ResolverType
+ end
+end
diff --git a/app/graphql/types/achievements/user_achievement_type.rb b/app/graphql/types/achievements/user_achievement_type.rb
index 7cdcb66576c..b92b2c42bee 100644
--- a/app/graphql/types/achievements/user_achievement_type.rb
+++ b/app/graphql/types/achievements/user_achievement_type.rb
@@ -48,6 +48,11 @@ module Types
Types::TimeType,
null: true,
description: 'Timestamp the achievement was revoked.'
+
+ field :priority,
+ GraphQL::Types::Int,
+ null: true,
+ description: 'Priority of the user achievement.'
end
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 445f26e2fcf..9d74ca13424 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -12,6 +12,7 @@ module Types
mount_mutation Mutations::Achievements::DeleteUserAchievement, alpha: { milestone: '16.1' }
mount_mutation Mutations::Achievements::Revoke, alpha: { milestone: '15.10' }
mount_mutation Mutations::Achievements::Update, alpha: { milestone: '15.11' }
+ mount_mutation Mutations::Achievements::UpdateUserAchievementPriorities, alpha: { milestone: '16.5' }
mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs
mount_mutation Mutations::AlertManagement::CreateAlertIssue
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index 9e5f6810aca..47d486265b0 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -160,7 +160,7 @@ module Types
description: "Achievements for the user. " \
"Only returns for namespaces where the `achievements` feature flag is enabled.",
extras: [:lookahead],
- resolver: ::Resolvers::Achievements::UserAchievementsResolver
+ resolver: ::Resolvers::Achievements::UserAchievementsForUserResolver
field :bio,
type: ::GraphQL::Types::String,
diff --git a/app/models/achievements/user_achievement.rb b/app/models/achievements/user_achievement.rb
index 08ebadaa6b0..8b15b25c183 100644
--- a/app/models/achievements/user_achievement.rb
+++ b/app/models/achievements/user_achievement.rb
@@ -15,6 +15,23 @@ module Achievements
optional: true
scope :not_revoked, -> { where(revoked_by_user_id: nil) }
+ scope :order_by_priority_asc, -> {
+ keyset_order = Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'priority',
+ order_expression: ::Achievements::UserAchievement.arel_table[:priority].asc,
+ nullable: :nulls_last,
+ distinct: false
+ ),
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'id',
+ order_expression: ::Achievements::UserAchievement.arel_table[:id].asc,
+ nullable: :not_nullable,
+ distinct: true
+ )
+ ])
+ reorder(keyset_order)
+ }
scope :order_by_id_asc, -> { order(id: :asc) }
def revoked?
diff --git a/app/policies/achievements/user_achievement_policy.rb b/app/policies/achievements/user_achievement_policy.rb
index 05650a05490..2710c9c0a5b 100644
--- a/app/policies/achievements/user_achievement_policy.rb
+++ b/app/policies/achievements/user_achievement_policy.rb
@@ -5,8 +5,17 @@ module Achievements
delegate { @subject.achievement.namespace }
delegate { @subject.user }
+ condition(:user_is_owner) { @subject.user == @user }
+
rule { can?(:read_user_profile) | can?(:admin_achievement) }.enable :read_user_achievement
- rule { ~can?(:read_achievement) }.prevent :read_user_achievement
+ rule { user_is_owner }.enable :update_owned_user_achievement
+
+ rule { can?(:update_owned_user_achievement) }.enable :update_user_achievement
+
+ rule { ~can?(:read_achievement) }.policy do
+ prevent :read_user_achievement
+ prevent :update_user_achievement
+ end
end
end
diff --git a/app/services/achievements/update_user_achievement_priorities_service.rb b/app/services/achievements/update_user_achievement_priorities_service.rb
new file mode 100644
index 00000000000..1165a1b3bf6
--- /dev/null
+++ b/app/services/achievements/update_user_achievement_priorities_service.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Achievements
+ class UpdateUserAchievementPrioritiesService
+ attr_reader :current_user, :user_achievements
+
+ def initialize(current_user, user_achievements)
+ @current_user = current_user
+ @user_achievements = user_achievements
+ end
+
+ def execute
+ return error_no_permissions unless allowed?
+
+ prioritized_user_achievements_map = Hash[user_achievements.map.with_index { |ua, idx| [ua.id, idx] }]
+
+ user_achievements_priorities_mapping = current_user.user_achievements.each_with_object({}) do |ua, result|
+ next if ua.priority.nil? && !prioritized_user_achievements_map.key?(ua.id)
+
+ result[ua] = { priority: prioritized_user_achievements_map.fetch(ua.id, nil) }
+ end
+
+ return ServiceResponse.success(payload: []) if user_achievements_priorities_mapping.empty?
+
+ ::Gitlab::Database::BulkUpdate.execute(%i[priority], user_achievements_priorities_mapping)
+
+ ServiceResponse.success(payload: user_achievements_priorities_mapping.keys.map(&:reload))
+ end
+
+ private
+
+ def allowed?
+ user_achievements.all? { |user_achievement| current_user&.can?(:update_owned_user_achievement, user_achievement) }
+ end
+
+ def error(message)
+ ServiceResponse.error(payload: user_achievements, message: Array(message))
+ end
+
+ def error_no_permissions
+ error("You can't update at least one of the given user achievements.")
+ end
+ end
+end