diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-21 09:09:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-21 09:09:48 +0300 |
commit | f9441cac3defdec8bdc34cefe7b4364fb49c0aff (patch) | |
tree | 372bbc9d0f33acd2c16095d713734ed85277bbfd /app | |
parent | 98e4ee99fe0ae9a8563d223c5cb7f0752d4a2604 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
16 files changed, 303 insertions, 66 deletions
diff --git a/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue b/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue index 1490d7e64f5..fb6bc38848c 100644 --- a/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue +++ b/app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue @@ -31,6 +31,11 @@ export default { alert: { ...alertDefaults }, }; }, + computed: { + similarOpenReports() { + return this.abuseReport.user?.similarOpenReports || []; + }, + }, methods: { showAlert(variant, message) { this.alert.visible = true; @@ -49,6 +54,7 @@ export default { <gl-alert v-if="alert.visible" :variant="alert.variant" class="gl-mt-4" @dismiss="closeAlert">{{ alert.message }}</gl-alert> + <report-header v-if="abuseReport.user" :user="abuseReport.user" @@ -56,7 +62,13 @@ export default { @showAlert="showAlert" /> <user-details v-if="abuseReport.user" :user="abuseReport.user" /> - <reported-content :report="abuseReport.report" :reporter="abuseReport.reporter" /> - <history-items :report="abuseReport.report" :reporter="abuseReport.reporter" /> + + <reported-content :report="abuseReport.report" data-testid="reported-content" /> + + <div v-for="report in similarOpenReports" :key="report.id" data-testid="similar-open-reports"> + <reported-content :report="report" /> + </div> + + <history-items :report="abuseReport.report" /> </section> </template> diff --git a/app/assets/javascripts/admin/abuse_report/components/history_items.vue b/app/assets/javascripts/admin/abuse_report/components/history_items.vue index 28b66db84a2..619a8bcfe92 100644 --- a/app/assets/javascripts/admin/abuse_report/components/history_items.vue +++ b/app/assets/javascripts/admin/abuse_report/components/history_items.vue @@ -16,13 +16,11 @@ export default { type: Object, required: true, }, - reporter: { - type: Object, - required: false, - default: null, - }, }, computed: { + reporter() { + return this.report.reporter; + }, reporterName() { return this.reporter?.name || this.$options.i18n.deletedReporter; }, @@ -35,7 +33,7 @@ export default { <!-- The styles `issuable-discussion`, `timeline`, `main-notes-list` and `notes` used below are declared in app/assets/stylesheets/pages/notes.scss --> <section class="gl-pt-6 issuable-discussion"> - <h2 class="gl-font-size-h1 gl-mt-0 gl-mb-2">{{ $options.i18n.activity }}</h2> + <h2 class="gl-font-lg gl-mt-0 gl-mb-2">{{ $options.i18n.activity }}</h2> <ul class="timeline main-notes-list notes"> <history-item icon="warning"> <div class="gl-display-flex gl-xs-flex-direction-column"> diff --git a/app/assets/javascripts/admin/abuse_report/components/reported_content.vue b/app/assets/javascripts/admin/abuse_report/components/reported_content.vue index f4f0fcac58f..84d6f25ac05 100644 --- a/app/assets/javascripts/admin/abuse_report/components/reported_content.vue +++ b/app/assets/javascripts/admin/abuse_report/components/reported_content.vue @@ -26,11 +26,6 @@ export default { type: Object, required: true, }, - reporter: { - type: Object, - required: false, - default: null, - }, }, data() { return { @@ -38,6 +33,9 @@ export default { }; }, computed: { + reporter() { + return this.report.reporter; + }, reporterName() { return this.reporter?.name || this.$options.i18n.deletedReporter; }, @@ -67,11 +65,12 @@ export default { <template> <div class="gl-pt-6"> <div - class="gl-pb-3 gl-display-flex gl-justify-content-space-between gl-xs-flex-direction-column" + class="gl-pb-3 gl-display-flex gl-justify-content-space-between gl-xs-flex-direction-column gl-align-items-center" > - <h2 class="gl-font-size-h1 gl-mt-0 gl-mb-2"> + <h2 class="gl-font-lg gl-mt-2 gl-mb-2"> {{ $options.i18n.reportTypes[reportType] }} </h2> + <div class="gl-display-flex gl-align-items-stretch gl-xs-flex-direction-column gl-mt-3 gl-sm-mt-0" > diff --git a/app/assets/javascripts/admin/abuse_report/components/user_details.vue b/app/assets/javascripts/admin/abuse_report/components/user_details.vue index 3dc03a8748f..fe0add1ba8d 100644 --- a/app/assets/javascripts/admin/abuse_report/components/user_details.vue +++ b/app/assets/javascripts/admin/abuse_report/components/user_details.vue @@ -39,19 +39,27 @@ export default { <template> <div class="gl-mt-6"> - <user-detail data-testid="createdAt" :label="$options.i18n.createdAt"> + <user-detail data-testid="created-at" :label="$options.i18n.createdAt"> <time-ago-tooltip :time="user.createdAt" /> </user-detail> + <user-detail data-testid="email" :label="$options.i18n.email"> <gl-link :href="`mailto:${user.email}`">{{ user.email }}</gl-link> </user-detail> + <user-detail data-testid="plan" :label="$options.i18n.plan" :value="user.plan" /> + <user-detail data-testid="verification" :label="$options.i18n.verification" :value="verificationState" /> - <user-detail v-if="user.creditCard" data-testid="creditCard" :label="$options.i18n.creditCard"> + + <user-detail + v-if="user.creditCard" + data-testid="credit-card-verification" + :label="$options.i18n.creditCard" + > <gl-sprintf :message="$options.i18n.registeredWith"> <template #name>{{ user.creditCard.name }}</template> </gl-sprintf> @@ -65,17 +73,18 @@ export default { </template> </gl-sprintf> </user-detail> + <user-detail - v-if="user.otherReports.length" - data-testid="otherReports" - :label="$options.i18n.otherReports" + v-if="user.pastClosedReports.length" + data-testid="past-closed-reports" + :label="$options.i18n.pastReports" > <div - v-for="(report, index) in user.otherReports" + v-for="(report, index) in user.pastClosedReports" :key="index" - :data-testid="`other-report-${index}`" + :data-testid="`past-report-${index}`" > - <gl-sprintf :message="$options.i18n.otherReport"> + <gl-sprintf :message="$options.i18n.reportedFor"> <template #reportLink="{ content }"> <gl-link :href="report.reportPath">{{ content }}</gl-link> </template> @@ -86,28 +95,33 @@ export default { </gl-sprintf> </div> </user-detail> + <user-detail - data-testid="normalLocation" + data-testid="normal-location" :label="$options.i18n.normalLocation" :value="user.mostUsedIp || user.lastSignInIp" /> + <user-detail - data-testid="lastSignInIp" + data-testid="last-sign-in-ip" :label="$options.i18n.lastSignInIp" :value="user.lastSignInIp" /> + <user-detail - data-testid="snippets" + data-testid="user-snippets-count" :label="$options.i18n.snippets" :value="$options.i18n.snippetsCount(user.snippetsCount)" /> + <user-detail - data-testid="groups" + data-testid="user-groups-count" :label="$options.i18n.groups" :value="$options.i18n.groupsCount(user.groupsCount)" /> + <user-detail - data-testid="notes" + data-testid="user-notes-count" :label="$options.i18n.notes" :value="$options.i18n.notesCount(user.notesCount)" /> diff --git a/app/assets/javascripts/admin/abuse_report/constants.js b/app/assets/javascripts/admin/abuse_report/constants.js index b290581598a..6cae6b24f20 100644 --- a/app/assets/javascripts/admin/abuse_report/constants.js +++ b/app/assets/javascripts/admin/abuse_report/constants.js @@ -58,7 +58,7 @@ export const USER_DETAILS_I18N = { plan: s__('AbuseReport|Tier'), verification: s__('AbuseReport|Verification'), creditCard: s__('AbuseReport|Credit card'), - otherReports: s__('AbuseReport|Abuse reports'), + pastReports: s__('AbuseReport|Past abuse reports'), normalLocation: s__('AbuseReport|Normal location'), lastSignInIp: s__('AbuseReport|Last login'), snippets: s__('AbuseReport|Snippets'), @@ -72,7 +72,7 @@ export const USER_DETAILS_I18N = { phone: s__('AbuseReport|Phone'), creditCard: s__('AbuseReport|Credit card'), }, - otherReport: s__( + reportedFor: s__( 'AbuseReport|%{reportLinkStart}Reported%{reportLinkEnd} for %{category} %{timeAgo}.', ), registeredWith: s__('AbuseReport|Registered with name %{name}.'), diff --git a/app/finders/organizations/groups_finder.rb b/app/finders/organizations/groups_finder.rb new file mode 100644 index 00000000000..2b59a3106a3 --- /dev/null +++ b/app/finders/organizations/groups_finder.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# Organizations::GroupsFinder +# +# Used to find Groups within an Organization +module Organizations + class GroupsFinder + # @param organization [Organizations::Organization] + # @param current_user [User] + # @param params [{ sort: { field: [String], direction: [String] }, search: [String] }] + def initialize(organization:, current_user:, params: {}) + @organization = organization + @current_user = current_user + @params = params + end + + def execute + return Group.none if organization.nil? || !authorized? + + filter_groups(all_accessible_groups) + .then { |groups| sort(groups) } + .then(&:with_route) + end + + private + + attr_reader :organization, :params, :current_user + + def all_accessible_groups + current_user.authorized_groups.in_organization(organization) + end + + def filter_groups(groups) + by_search(groups) + end + + def by_search(groups) + return groups unless params[:search].present? + + groups.search(params[:search]) + end + + def sort(groups) + return default_sort_order(groups) if params[:sort].blank? + + field = params[:sort][:field] + direction = params[:sort][:direction] + groups.reorder(field => direction) # rubocop: disable CodeReuse/ActiveRecord + end + + def default_sort_order(groups) + groups.sort_by_attribute('name_asc') + end + + def authorized? + Ability.allowed?(current_user, :read_organization, organization) + end + end +end diff --git a/app/graphql/resolvers/organizations/groups_resolver.rb b/app/graphql/resolvers/organizations/groups_resolver.rb new file mode 100644 index 00000000000..0f50713b9b4 --- /dev/null +++ b/app/graphql/resolvers/organizations/groups_resolver.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Resolvers + module Organizations + class GroupsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include ResolvesGroups + + type Types::GroupType.connection_type, null: true + + authorize :read_group + + argument :search, + GraphQL::Types::String, + required: false, + description: 'Search query for group name or full path.', + alpha: { milestone: '16.4' } + + argument :sort, + Types::Organizations::GroupSortEnum, + description: 'Criteria to sort organization groups by.', + required: false, + default_value: { field: 'name', direction: :asc }, + alpha: { milestone: '16.4' } + + private + + def resolve_groups(**args) + return Group.none if Feature.disabled?(:resolve_organization_groups, context[:current_user]) + + ::Organizations::GroupsFinder + .new(organization: object, current_user: context[:current_user], params: args) + .execute + end + end + end +end diff --git a/app/graphql/resolvers/organizations/organization_resolver.rb b/app/graphql/resolvers/organizations/organization_resolver.rb new file mode 100644 index 00000000000..9194d9a32c5 --- /dev/null +++ b/app/graphql/resolvers/organizations/organization_resolver.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Resolvers + module Organizations + class OrganizationResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + authorize :read_organization + + type Types::Organizations::OrganizationType, null: true + + argument :id, + Types::GlobalIDType[::Organizations::Organization], + required: true, + description: 'ID of the organization.' + + def resolve(id:) + authorized_find!(id: id) + end + end + end +end diff --git a/app/graphql/types/organizations/group_sort_enum.rb b/app/graphql/types/organizations/group_sort_enum.rb new file mode 100644 index 00000000000..8fb2f553539 --- /dev/null +++ b/app/graphql/types/organizations/group_sort_enum.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Organizations + class GroupSortEnum < BaseEnum + graphql_name 'OrganizationGroupSort' + description 'Values for sorting organization groups' + + sortable_fields = ['ID', 'Name', 'Path', 'Updated at', 'Created at'] + + sortable_fields.each do |field| + value "#{field.upcase.tr(' ', '_')}_ASC", + value: { field: field.downcase.tr(' ', '_'), direction: :asc }, + description: "#{field} in ascending order.", + alpha: { milestone: '16.4' } + + value "#{field.upcase.tr(' ', '_')}_DESC", + value: { field: field.downcase.tr(' ', '_'), direction: :desc }, + description: "#{field} in descending order.", + alpha: { milestone: '16.4' } + end + end + end +end diff --git a/app/graphql/types/organizations/organization_type.rb b/app/graphql/types/organizations/organization_type.rb new file mode 100644 index 00000000000..791fddc5266 --- /dev/null +++ b/app/graphql/types/organizations/organization_type.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Types + module Organizations + class OrganizationType < BaseObject + graphql_name 'Organization' + + authorize :read_organization + + field :groups, + Types::GroupType.connection_type, + null: false, + description: 'Groups within this organization that the user has access to.', + alpha: { milestone: '16.4' }, + resolver: ::Resolvers::Organizations::GroupsResolver + field :id, + GraphQL::Types::ID, + null: false, + description: 'ID of the organization.', + alpha: { milestone: '16.4' } + field :name, + GraphQL::Types::String, + null: false, + description: 'Name of the organization.', + alpha: { milestone: '16.4' } + field :path, + GraphQL::Types::String, + null: false, + description: 'Path of the organization.', + alpha: { milestone: '16.4' } + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 38b8973034d..e3dd0211029 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -96,6 +96,12 @@ module Types required: true, description: 'Global ID of the note.' end + field :organization, + Types::Organizations::OrganizationType, + null: true, + resolver: Resolvers::Organizations::OrganizationResolver, + description: "Find an organization.", + alpha: { milestone: '16.4' } field :package, description: 'Find a package. This field can only be resolved for one query in any single request. Returns `null` if a package has no `default` status.', resolver: Resolvers::PackageDetailsResolver diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index 75c90d370c3..afac53762a7 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -61,8 +61,8 @@ class AbuseReport < ApplicationRecord validates :screenshot, file_size: { maximum: MAX_FILE_SIZE } validate :validate_screenshot_is_image - scope :by_user_id, ->(id) { where(user_id: id) } - scope :by_reporter_id, ->(id) { where(reporter_id: id) } + scope :by_user_id, ->(user_id) { where(user_id: user_id) } + scope :by_reporter_id, ->(reporter_id) { where(reporter_id: reporter_id) } scope :by_category, ->(category) { where(category: category) } scope :with_users, -> { includes(:reporter, :user) } @@ -141,8 +141,14 @@ class AbuseReport < ApplicationRecord end end - def other_reports_for_user - user.abuse_reports.id_not_in(id) + def past_closed_reports_for_user + user.abuse_reports.closed.id_not_in(id) + end + + def similar_open_reports_for_user + return AbuseReport.none unless open? + + user.abuse_reports.open.by_category(category).id_not_in(id).includes(:reporter) end private diff --git a/app/models/namespace.rb b/app/models/namespace.rb index a7d03c3688a..be39b894ef9 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -167,6 +167,7 @@ class Namespace < ApplicationRecord scope :include_route, -> { includes(:route) } scope :by_parent, -> (parent) { where(parent_id: parent) } scope :filter_by_path, -> (query) { where('lower(path) = :query', query: query.downcase) } + scope :in_organization, -> (organization) { where(organization: organization) } scope :with_statistics, -> do joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id') diff --git a/app/models/organizations/organization.rb b/app/models/organizations/organization.rb index 9f2119949fb..489fd6e0da7 100644 --- a/app/models/organizations/organization.rb +++ b/app/models/organizations/organization.rb @@ -38,7 +38,7 @@ module Organizations end def user?(user) - users.exists?(user.id) + organization_users.exists?(user: user) end private diff --git a/app/serializers/admin/abuse_report_details_entity.rb b/app/serializers/admin/abuse_report_details_entity.rb index 3efb8508e5e..8a67aabda9e 100644 --- a/app/serializers/admin/abuse_report_details_entity.rb +++ b/app/serializers/admin/abuse_report_details_entity.rb @@ -8,17 +8,21 @@ module Admin expose :details, merge: true do |report| UserEntity.represent(report.user, only: [:name, :username, :avatar_url, :email, :created_at, :last_activity_on]) end + expose :path do |report| user_path(report.user) end + expose :admin_path do |report| admin_user_path(report.user) end + expose :plan do |report| if Gitlab::CurrentSettings.current_application_settings.try(:should_check_namespace_plan?) report.user.namespace&.actual_plan&.title end end + expose :verification_state do expose :email do |report| report.user.confirmed? @@ -30,6 +34,7 @@ module Admin report.user.credit_card_validation.present? end end + expose :credit_card, if: ->(report) { report.user.credit_card_validation&.holder_name } do expose :name do |report| report.user.credit_card_validation.holder_name @@ -41,55 +46,38 @@ module Admin card_match_admin_user_path(report.user) if Gitlab.ee? end end - expose :other_reports do |report| - AbuseReportEntity.represent(report.other_reports_for_user, only: [:created_at, :category, :report_path]) + + expose :past_closed_reports do |report| + AbuseReportEntity.represent(report.past_closed_reports_for_user, only: [:created_at, :category, :report_path]) + end + + expose :similar_open_reports, if: ->(report) { report.open? } do |report| + ReportedContentEntity.represent(report.similar_open_reports_for_user) end + expose :most_used_ip do |report| AuthenticationEvent.most_used_ip_address_for_user(report.user) end + expose :last_sign_in_ip do |report| report.user.last_sign_in_ip end + expose :snippets_count do |report| report.user.snippets.count end + expose :groups_count do |report| report.user.groups.count end + expose :notes_count do |report| report.user.notes.count end end - expose :reporter, if: ->(report) { report.reporter } do - expose :details, merge: true do |report| - UserEntity.represent(report.reporter, only: [:name, :username, :avatar_url]) - end - expose :path do |report| - user_path(report.reporter) - end - end - - expose :report do - expose :status - expose :message - expose :created_at, as: :reported_at - expose :category - expose :report_type, as: :type - expose :reported_content, as: :content - expose :reported_from_url, as: :url - expose :screenshot_path, as: :screenshot - - # Kept for backwards compatibility. - # TODO: See https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/167?work_item_iid=443 - # In 16.4 remove or re-use this field after frontend has migrated to using moderate_user_path - expose :update_path do |report| - admin_abuse_report_path(report) - end - - expose :moderate_user_path do |report| - moderate_user_admin_abuse_report_path(report) - end + expose :report do |report| + ReportedContentEntity.represent(report) end end end diff --git a/app/serializers/admin/reported_content_entity.rb b/app/serializers/admin/reported_content_entity.rb new file mode 100644 index 00000000000..0e86a1434f8 --- /dev/null +++ b/app/serializers/admin/reported_content_entity.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Admin + class ReportedContentEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :status + expose :message + expose :created_at, as: :reported_at + expose :category + expose :report_type, as: :type + expose :reported_content, as: :content + expose :reported_from_url, as: :url + expose :screenshot_path, as: :screenshot + + expose :reporter, if: ->(report) { report.reporter } do + expose :details, merge: true do |report| + UserEntity.represent(report.reporter, only: [:name, :username, :avatar_url]) + end + + expose :path do |report| + user_path(report.reporter) + end + end + + # Kept for backwards compatibility. + # TODO: See https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/167?work_item_iid=443 + # In 16.4 remove or re-use this field after frontend has migrated to using moderate_user_path + expose :update_path do |report| + admin_abuse_report_path(report) + end + + expose :moderate_user_path do |report| + moderate_user_admin_abuse_report_path(report) + end + end +end |