diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-21 12:10:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-21 12:10:54 +0300 |
commit | 7e29b8f3fcc32a6adcf735a5a9069de5d9d814d6 (patch) | |
tree | db9cd61369d239bf092628a8796395374f0c4468 /app | |
parent | fccfc5332f11e87433ada819c2467713f2dbb8f3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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 |