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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-11-14 11:41:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-14 11:41:52 +0300
commit585826cb22ecea5998a2c2a4675735c94bdeedac (patch)
tree5b05f0b30d33cef48963609e8a18a4dff260eab3 /app/assets/javascripts/admin
parentdf221d036e5d0c6c0ee4d55b9c97f481ee05dee8 (diff)
Add latest changes from gitlab-org/gitlab@16-6-stable-eev16.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/admin')
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/abuse_report_app.vue7
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue92
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue2
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/labels_select.vue2
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue104
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue81
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note_body.vue48
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/report_details.vue2
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/reported_content.vue4
-rw-r--r--app/assets/javascripts/admin/abuse_report/constants.js2
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/abuse_report.query.graphql (renamed from app/assets/javascripts/admin/abuse_report/components/graphql/abuse_report.query.graphql)1
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/abuse_report_labels.query.graphql (renamed from app/assets/javascripts/admin/abuse_report/components/graphql/abuse_report_labels.query.graphql)0
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/create_abuse_report_label.mutation.graphql (renamed from app/assets/javascripts/admin/abuse_report/components/graphql/create_abuse_report_label.mutation.graphql)0
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql30
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql3
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_notes.query.graphql18
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql18
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/notes/delete_abuse_report_note.fragment.graphql8
-rw-r--r--app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql10
-rw-r--r--app/assets/javascripts/admin/background_migrations/index.js2
-rw-r--r--app/assets/javascripts/admin/broadcast_messages/components/message_form.vue7
-rw-r--r--app/assets/javascripts/admin/users/components/actions/index.js4
-rw-r--r--app/assets/javascripts/admin/users/components/actions/trust_user.vue62
-rw-r--r--app/assets/javascripts/admin/users/components/actions/untrust_user.vue56
-rw-r--r--app/assets/javascripts/admin/users/components/app.vue63
-rw-r--r--app/assets/javascripts/admin/users/components/user_avatar.vue67
-rw-r--r--app/assets/javascripts/admin/users/components/users_table.vue142
-rw-r--r--app/assets/javascripts/admin/users/constants.js6
28 files changed, 618 insertions, 223 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 3c46de7c2be..f0540ffa71e 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
@@ -7,6 +7,7 @@ import ReportDetails from './report_details.vue';
import ReportedContent from './reported_content.vue';
import ActivityEventsList from './activity_events_list.vue';
import ActivityHistoryItem from './activity_history_item.vue';
+import AbuseReportNotes from './abuse_report_notes.vue';
const alertDefaults = {
visible: false,
@@ -24,6 +25,7 @@ export default {
ReportedContent,
ActivityEventsList,
ActivityHistoryItem,
+ AbuseReportNotes,
},
mixins: [glFeatureFlagsMixin()],
props: {
@@ -96,5 +98,10 @@ export default {
/>
</template>
</activity-events-list>
+
+ <abuse-report-notes
+ v-if="glFeatures.abuseReportNotes"
+ :abuse-report-id="abuseReport.report.globalId"
+ />
</section>
</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue b/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
new file mode 100644
index 00000000000..80af7d7400a
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
@@ -0,0 +1,92 @@
+<script>
+import { uniqueId } from 'lodash';
+import { __ } from '~/locale';
+import { createAlert } from '~/alert';
+import SkeletonLoadingContainer from '~/vue_shared/components/notes/skeleton_note.vue';
+import { SKELETON_NOTES_COUNT } from '~/admin/abuse_report/constants';
+import abuseReportNotesQuery from '../graphql/notes/abuse_report_notes.query.graphql';
+import AbuseReportDiscussion from './notes/abuse_report_discussion.vue';
+
+export default {
+ name: 'AbuseReportNotes',
+ SKELETON_NOTES_COUNT,
+ i18n: {
+ fetchError: __('An error occurred while fetching comments, please try again.'),
+ },
+ components: {
+ SkeletonLoadingContainer,
+ AbuseReportDiscussion,
+ },
+ props: {
+ abuseReportId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ addNoteKey: uniqueId(`abuse-report-add-note-${this.abuseReportId}`),
+ };
+ },
+ apollo: {
+ abuseReportNotes: {
+ query: abuseReportNotesQuery,
+ variables() {
+ return {
+ id: this.abuseReportId,
+ };
+ },
+ update(data) {
+ return data.abuseReport?.discussions || [];
+ },
+ skip() {
+ return !this.abuseReportId;
+ },
+ error() {
+ createAlert({ message: this.$options.i18n.fetchError });
+ },
+ },
+ },
+ computed: {
+ initialLoading() {
+ return this.$apollo.queries.abuseReportNotes.loading;
+ },
+ notesArray() {
+ return this.abuseReportNotes?.nodes || [];
+ },
+ },
+ methods: {
+ getDiscussionKey(discussion) {
+ const discussionId = discussion.notes.nodes[0].id;
+ return discussionId.split('/')[discussionId.split('/').length - 1];
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="issuable-discussion gl-mb-5 gl-clearfix!">
+ <template v-if="initialLoading">
+ <ul class="notes main-notes-list timeline">
+ <skeleton-loading-container
+ v-for="index in $options.SKELETON_NOTES_COUNT"
+ :key="index"
+ class="note-skeleton"
+ />
+ </ul>
+ </template>
+
+ <template v-else>
+ <ul class="notes main-notes-list timeline">
+ <abuse-report-discussion
+ v-for="discussion in notesArray"
+ :key="getDiscussionKey(discussion)"
+ :discussion="discussion.notes.nodes"
+ :abuse-report-id="abuseReportId"
+ />
+ </ul>
+ </template>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue b/app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue
index 8c4c1da28b8..2206e600543 100644
--- a/app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/activity_events_list.vue
@@ -11,7 +11,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-lg gl-mt-0 gl-mb-2">{{ $options.i18n.activity }}</h2>
+ <h2 class="gl-font-size-h1 gl-mt-0 gl-mb-4">{{ $options.i18n.activity }}</h2>
<ul class="timeline main-notes-list notes">
<slot name="history-items"></slot>
</ul>
diff --git a/app/assets/javascripts/admin/abuse_report/components/labels_select.vue b/app/assets/javascripts/admin/abuse_report/components/labels_select.vue
index 747c9a1a947..d2d143f0460 100644
--- a/app/assets/javascripts/admin/abuse_report/components/labels_select.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/labels_select.vue
@@ -11,7 +11,7 @@ import DropdownContentsCreateView from '~/sidebar/components/labels/labels_selec
import DropdownHeader from '~/sidebar/components/labels/labels_select_widget/dropdown_header.vue';
import DropdownFooter from '~/sidebar/components/labels/labels_select_widget/dropdown_footer.vue';
import DropdownWidget from '~/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue';
-import abuseReportLabelsQuery from './graphql/abuse_report_labels.query.graphql';
+import abuseReportLabelsQuery from '../graphql/abuse_report_labels.query.graphql';
export default {
components: {
diff --git a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue
new file mode 100644
index 00000000000..4d24471fa43
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue
@@ -0,0 +1,104 @@
+<script>
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import DiscussionNotesRepliesWrapper from '~/notes/components/discussion_notes_replies_wrapper.vue';
+import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
+import AbuseReportNote from './abuse_report_note.vue';
+
+export default {
+ name: 'AbuseReportDiscussion',
+ components: {
+ TimelineEntryItem,
+ DiscussionNotesRepliesWrapper,
+ ToggleRepliesWidget,
+ AbuseReportNote,
+ },
+ props: {
+ abuseReportId: {
+ type: String,
+ required: true,
+ },
+ discussion: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isExpanded: true,
+ };
+ },
+ computed: {
+ note() {
+ return this.discussion[0];
+ },
+ noteId() {
+ return getIdFromGraphQLId(this.note.id);
+ },
+ replies() {
+ if (this.discussion?.length > 1) {
+ return this.discussion.slice(1);
+ }
+ return null;
+ },
+ hasReplies() {
+ return Boolean(this.replies?.length);
+ },
+ discussionId() {
+ return this.discussion[0]?.discussion?.id || '';
+ },
+ },
+ methods: {
+ toggleDiscussion() {
+ this.isExpanded = !this.isExpanded;
+ },
+ },
+};
+</script>
+
+<template>
+ <abuse-report-note
+ v-if="!hasReplies"
+ :note="note"
+ :abuse-report-id="abuseReportId"
+ class="gl-mb-4"
+ />
+ <timeline-entry-item v-else :data-note-id="noteId" class="note note-discussion gl-px-0">
+ <div class="timeline-content">
+ <div class="discussion">
+ <div class="discussion-body">
+ <div class="discussion-wrapper">
+ <div class="discussion-notes">
+ <ul class="notes">
+ <abuse-report-note
+ :note="note"
+ :discussion-id="discussionId"
+ :abuse-report-id="abuseReportId"
+ class="gl-mb-4"
+ />
+ <discussion-notes-replies-wrapper>
+ <toggle-replies-widget
+ v-if="hasReplies"
+ :collapsed="!isExpanded"
+ :replies="replies"
+ @toggle="toggleDiscussion({ discussionId })"
+ />
+ <template v-if="isExpanded">
+ <template v-for="reply in replies">
+ <abuse-report-note
+ :key="reply.id"
+ :discussion-id="discussionId"
+ :note="reply"
+ :abuse-report-id="abuseReportId"
+ />
+ </template>
+ </template>
+ </discussion-notes-replies-wrapper>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </timeline-entry-item>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue
new file mode 100644
index 00000000000..6da3017e11e
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue
@@ -0,0 +1,81 @@
+<script>
+import { GlAvatarLink, GlAvatar } from '@gitlab/ui';
+import SafeHtml from '~/vue_shared/directives/safe_html';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import NoteHeader from '~/notes/components/note_header.vue';
+import NoteBody from './abuse_report_note_body.vue';
+
+export default {
+ name: 'AbuseReportNote',
+ directives: {
+ SafeHtml,
+ },
+ components: {
+ GlAvatarLink,
+ GlAvatar,
+ TimelineEntryItem,
+ NoteHeader,
+ NoteBody,
+ },
+ props: {
+ abuseReportId: {
+ type: String,
+ required: true,
+ },
+ note: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ noteAnchorId() {
+ return `note_${getIdFromGraphQLId(this.note.id)}`;
+ },
+ author() {
+ return this.note.author;
+ },
+ authorId() {
+ return getIdFromGraphQLId(this.author.id);
+ },
+ },
+};
+</script>
+
+<template>
+ <timeline-entry-item :id="noteAnchorId" class="note note-wrapper note-comment">
+ <div :key="note.id" class="timeline-avatar gl-float-left">
+ <gl-avatar-link
+ :href="author.webUrl"
+ :data-user-id="authorId"
+ :data-username="author.username"
+ class="js-user-link"
+ >
+ <gl-avatar
+ :src="author.avatarUrl"
+ :entity-name="author.username"
+ :alt="author.name"
+ :size="32"
+ />
+ </gl-avatar-link>
+ </div>
+ <div class="timeline-content">
+ <div data-testid="note-wrapper">
+ <div class="note-header">
+ <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>
+
+ <div class="timeline-discussion-body">
+ <note-body ref="noteBody" :note="note" />
+ </div>
+ </div>
+ </div>
+ </timeline-entry-item>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note_body.vue b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note_body.vue
new file mode 100644
index 00000000000..ab3d7f5fa6c
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note_body.vue
@@ -0,0 +1,48 @@
+<script>
+import SafeHtml from '~/vue_shared/directives/safe_html';
+import { renderGFM } from '~/behaviors/markdown/render_gfm';
+
+export default {
+ name: 'AbuseReportNoteBody',
+ directives: {
+ SafeHtml,
+ },
+ props: {
+ note: {
+ type: Object,
+ required: true,
+ },
+ },
+ watch: {
+ 'note.bodyHtml': {
+ immediate: true,
+ async handler(newVal, oldVal) {
+ if (newVal === oldVal) {
+ return;
+ }
+ await this.$nextTick();
+ this.renderGFM();
+ },
+ },
+ },
+ methods: {
+ renderGFM() {
+ renderGFM(this.$refs['note-body']);
+ gl?.lazyLoader?.searchLazyImages();
+ },
+ },
+ safeHtmlConfig: {
+ ADD_TAGS: ['use', 'gl-emoji', 'copy-code'],
+ },
+};
+</script>
+
+<template>
+ <div ref="note-body" class="note-body">
+ <div
+ v-safe-html:[$options.safeHtmlConfig]="note.bodyHtml"
+ class="note-text md"
+ data-testid="abuse-report-note-body"
+ ></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/report_details.vue b/app/assets/javascripts/admin/abuse_report/components/report_details.vue
index 10e1dca7f91..89017e6cbd4 100644
--- a/app/assets/javascripts/admin/abuse_report/components/report_details.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/report_details.vue
@@ -1,8 +1,8 @@
<script>
import { __ } from '~/locale';
import { createAlert } from '~/alert';
+import abuseReportQuery from '../graphql/abuse_report.query.graphql';
import LabelsSelect from './labels_select.vue';
-import abuseReportQuery from './graphql/abuse_report.query.graphql';
export default {
name: 'ReportDetails',
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 84d6f25ac05..99c8b3ece10 100644
--- a/app/assets/javascripts/admin/abuse_report/components/reported_content.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/reported_content.vue
@@ -67,7 +67,7 @@ export default {
<div
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-lg gl-mt-2 gl-mb-2">
+ <h2 class="gl-font-size-h1 gl-mt-2 gl-mb-2">
{{ $options.i18n.reportTypes[reportType] }}
</h2>
@@ -128,7 +128,7 @@ export default {
</gl-link>
<time-ago-tooltip
:time="report.reportedAt"
- class="gl-ml-3 gl-text-secondary gl-xs-w-full"
+ class="gl-ml-3 gl-text-secondary gl-w-full gl-sm-w-auto"
/>
</div>
</div>
diff --git a/app/assets/javascripts/admin/abuse_report/constants.js b/app/assets/javascripts/admin/abuse_report/constants.js
index f028408bed7..c56ea678b1d 100644
--- a/app/assets/javascripts/admin/abuse_report/constants.js
+++ b/app/assets/javascripts/admin/abuse_report/constants.js
@@ -111,3 +111,5 @@ export const HISTORY_ITEMS_I18N = {
reportedByForCategory: s__('AbuseReport|Reported by %{name} for %{category}.'),
deletedReporter: s__('AbuseReport|No user found'),
};
+
+export const SKELETON_NOTES_COUNT = 5;
diff --git a/app/assets/javascripts/admin/abuse_report/components/graphql/abuse_report.query.graphql b/app/assets/javascripts/admin/abuse_report/graphql/abuse_report.query.graphql
index f5b075cb9af..640eec718f8 100644
--- a/app/assets/javascripts/admin/abuse_report/components/graphql/abuse_report.query.graphql
+++ b/app/assets/javascripts/admin/abuse_report/graphql/abuse_report.query.graphql
@@ -1,5 +1,6 @@
query abuseReportQuery($id: AbuseReportID!) {
abuseReport(id: $id) {
+ id
labels {
nodes {
id
diff --git a/app/assets/javascripts/admin/abuse_report/components/graphql/abuse_report_labels.query.graphql b/app/assets/javascripts/admin/abuse_report/graphql/abuse_report_labels.query.graphql
index 4e724b4db2c..4e724b4db2c 100644
--- a/app/assets/javascripts/admin/abuse_report/components/graphql/abuse_report_labels.query.graphql
+++ b/app/assets/javascripts/admin/abuse_report/graphql/abuse_report_labels.query.graphql
diff --git a/app/assets/javascripts/admin/abuse_report/components/graphql/create_abuse_report_label.mutation.graphql b/app/assets/javascripts/admin/abuse_report/graphql/create_abuse_report_label.mutation.graphql
index 0781b8e634b..0781b8e634b 100644
--- a/app/assets/javascripts/admin/abuse_report/components/graphql/create_abuse_report_label.mutation.graphql
+++ b/app/assets/javascripts/admin/abuse_report/graphql/create_abuse_report_label.mutation.graphql
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql
new file mode 100644
index 00000000000..84b57b4ed79
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql
@@ -0,0 +1,30 @@
+#import "~/graphql_shared/fragments/author.fragment.graphql"
+#import "./abuse_report_note_permissions.fragment.graphql"
+
+fragment AbuseReportNote on Note {
+ id
+ body
+ bodyHtml
+ createdAt
+ lastEditedAt
+ url
+ resolved
+ author {
+ ...Author
+ }
+ lastEditedBy {
+ ...Author
+ webPath
+ }
+ userPermissions {
+ ...AbuseReportNotePermissions
+ }
+ discussion {
+ id
+ notes {
+ nodes {
+ id
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql
new file mode 100644
index 00000000000..01436436b93
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql
@@ -0,0 +1,3 @@
+fragment AbuseReportNotePermissions on NotePermissions {
+ adminNote
+}
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_notes.query.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_notes.query.graphql
new file mode 100644
index 00000000000..3a13ac1f37a
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_notes.query.graphql
@@ -0,0 +1,18 @@
+#import "./abuse_report_note.fragment.graphql"
+
+query abuseReportNotes($id: AbuseReportID!) {
+ abuseReport(id: $id) {
+ id
+ discussions {
+ nodes {
+ id
+ replyId
+ notes {
+ nodes {
+ ...AbuseReportNote
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql
new file mode 100644
index 00000000000..53ac9468e08
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql
@@ -0,0 +1,18 @@
+#import "./abuse_report_note.fragment.graphql"
+
+mutation createAbuseReportNote($input: CreateNoteInput!) {
+ createNote(input: $input) {
+ note {
+ id
+ discussion {
+ id
+ notes {
+ nodes {
+ ...AbuseReportNote
+ }
+ }
+ }
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/delete_abuse_report_note.fragment.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/delete_abuse_report_note.fragment.graphql
new file mode 100644
index 00000000000..e8ff2933159
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/delete_abuse_report_note.fragment.graphql
@@ -0,0 +1,8 @@
+mutation deleteAbuseReportNote($input: DestroyNoteInput!) {
+ destroyNote(input: $input) {
+ errors
+ note {
+ id
+ }
+ }
+}
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql
new file mode 100644
index 00000000000..e11165074c9
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql
@@ -0,0 +1,10 @@
+#import "./abuse_report_note.fragment.graphql"
+
+mutation updateAbuseReportNote($input: UpdateNoteInput!) {
+ updateNote(input: $input) {
+ note {
+ ...AbuseReportNote
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/admin/background_migrations/index.js b/app/assets/javascripts/admin/background_migrations/index.js
index 4ddd8f17c9a..890df17080d 100644
--- a/app/assets/javascripts/admin/background_migrations/index.js
+++ b/app/assets/javascripts/admin/background_migrations/index.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import * as Sentry from '@sentry/browser';
+import * as Sentry from '~/sentry/sentry_browser_wrapper';
import Translate from '~/vue_shared/translate';
import BackgroundMigrationsDatabaseListbox from './components/database_listbox.vue';
diff --git a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
index 2c555aca3c0..753b1fb1819 100644
--- a/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
+++ b/app/assets/javascripts/admin/broadcast_messages/components/message_form.vue
@@ -107,7 +107,7 @@ export default {
targetSelected: '',
targetPath: this.broadcastMessage.targetPath,
targetAccessLevels: this.broadcastMessage.targetAccessLevels,
- targetAccessLevelOptions: this.targetAccessLevelOptions.map(([text, value]) => ({
+ targetAccessLevelCheckBoxGroupOptions: this.targetAccessLevelOptions.map(([text, value]) => ({
text,
value,
})),
@@ -324,7 +324,10 @@ export default {
:state="!isValidated || targetRolesValid"
data-testid="target-roles-checkboxes"
>
- <gl-form-checkbox-group v-model="targetAccessLevels" :options="targetAccessLevelOptions" />
+ <gl-form-checkbox-group
+ v-model="targetAccessLevels"
+ :options="targetAccessLevelCheckBoxGroupOptions"
+ />
</gl-form-group>
<gl-form-group
diff --git a/app/assets/javascripts/admin/users/components/actions/index.js b/app/assets/javascripts/admin/users/components/actions/index.js
index 4e63a85df89..633bc4d8b15 100644
--- a/app/assets/javascripts/admin/users/components/actions/index.js
+++ b/app/assets/javascripts/admin/users/components/actions/index.js
@@ -9,6 +9,8 @@ import Reject from './reject.vue';
import Unban from './unban.vue';
import Unblock from './unblock.vue';
import Unlock from './unlock.vue';
+import Trust from './trust_user.vue';
+import Untrust from './untrust_user.vue';
export default {
Activate,
@@ -22,4 +24,6 @@ export default {
Unblock,
Unlock,
Reject,
+ Trust,
+ Untrust,
};
diff --git a/app/assets/javascripts/admin/users/components/actions/trust_user.vue b/app/assets/javascripts/admin/users/components/actions/trust_user.vue
new file mode 100644
index 00000000000..41ff8d4120d
--- /dev/null
+++ b/app/assets/javascripts/admin/users/components/actions/trust_user.vue
@@ -0,0 +1,62 @@
+<script>
+import { GlDisclosureDropdownItem } from '@gitlab/ui';
+import { sprintf, s__, __ } from '~/locale';
+import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub';
+import { I18N_USER_ACTIONS } from '../../constants';
+
+// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
+const messageHtml = `
+ <p>${s__('AdminUsers|When not being monitored for spam:')}</p>
+ <ul>
+ <li>${s__(
+ 'AdminUsers|The user can create issues, notes, snippets, and merge requests that appear to be spam without being blocked.',
+ )}</li>
+ </ul>
+ <p>${s__('AdminUsers|You can untrust this user in the future.')}</p>
+`;
+
+export default {
+ components: {
+ GlDisclosureDropdownItem,
+ },
+ props: {
+ username: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ onClick() {
+ eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, {
+ path: this.path,
+ method: 'put',
+ modalAttributes: {
+ title: sprintf(s__('AdminUsers|Stop monitoring %{username} for possible spam?'), {
+ username: this.username,
+ }),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.trust,
+ attributes: { variant: 'confirm' },
+ },
+ messageHtml,
+ },
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-disclosure-dropdown-item @action="onClick">
+ <template #list-item>
+ <slot></slot>
+ </template>
+ </gl-disclosure-dropdown-item>
+</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/untrust_user.vue b/app/assets/javascripts/admin/users/components/actions/untrust_user.vue
new file mode 100644
index 00000000000..da59833af07
--- /dev/null
+++ b/app/assets/javascripts/admin/users/components/actions/untrust_user.vue
@@ -0,0 +1,56 @@
+<script>
+import { GlDisclosureDropdownItem } from '@gitlab/ui';
+import { sprintf, s__, __ } from '~/locale';
+import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub';
+import { I18N_USER_ACTIONS } from '../../constants';
+
+// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
+const messageHtml = `<p>${s__(
+ 'AdminUsers|You can trust this user in the future if necessary.',
+)}</p>`;
+
+export default {
+ components: {
+ GlDisclosureDropdownItem,
+ },
+ props: {
+ username: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ onClick() {
+ eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, {
+ path: this.path,
+ method: 'put',
+ modalAttributes: {
+ title: sprintf(s__('AdminUsers|Re-enable spam monitoring for %{username}?'), {
+ username: this.username,
+ }),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.untrust,
+ attributes: { variant: 'confirm' },
+ },
+ messageHtml,
+ },
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-disclosure-dropdown-item @action="onClick">
+ <template #list-item>
+ <slot></slot>
+ </template>
+ </gl-disclosure-dropdown-item>
+</template>
diff --git a/app/assets/javascripts/admin/users/components/app.vue b/app/assets/javascripts/admin/users/components/app.vue
index a3abd904a6b..b0caffb6ca6 100644
--- a/app/assets/javascripts/admin/users/components/app.vue
+++ b/app/assets/javascripts/admin/users/components/app.vue
@@ -1,9 +1,15 @@
<script>
-import UsersTable from './users_table.vue';
+import { createAlert } from '~/alert';
+import { s__ } from '~/locale';
+import { convertNodeIdsFromGraphQLIds } from '~/graphql_shared/utils';
+import UsersTable from '~/vue_shared/components/users_table/users_table.vue';
+import getUsersGroupCountsQuery from '../graphql/queries/get_users_group_counts.query.graphql';
+import UserActions from './user_actions.vue';
export default {
components: {
UsersTable,
+ UserActions,
},
props: {
users: {
@@ -16,11 +22,64 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ groupCounts: {},
+ };
+ },
+ apollo: {
+ groupCounts: {
+ query: getUsersGroupCountsQuery,
+ variables() {
+ return {
+ usernames: this.users.map((user) => user.username),
+ };
+ },
+ update(data) {
+ const nodes = data?.users?.nodes || [];
+ const parsedIds = convertNodeIdsFromGraphQLIds(nodes);
+
+ return parsedIds.reduce((acc, { id, groupCount }) => {
+ acc[id] = groupCount || 0;
+ return acc;
+ }, {});
+ },
+ error(error) {
+ createAlert({
+ message: this.$options.i18n.groupCountFetchError,
+ captureError: true,
+ error,
+ });
+ },
+ skip() {
+ return !this.users.length;
+ },
+ },
+ },
+ computed: {
+ groupCountsLoading() {
+ return this.$apollo.queries.groupCounts.loading;
+ },
+ },
+ i18n: {
+ groupCountFetchError: s__(
+ 'AdminUsers|Could not load user group counts. Please refresh the page to try again.',
+ ),
+ },
};
</script>
<template>
<div>
- <users-table :users="users" :paths="paths" />
+ <users-table
+ :users="users"
+ :admin-user-path="paths.adminUser"
+ :group-counts="groupCounts"
+ :group-counts-loading="groupCountsLoading"
+ >
+ <template #user-actions="{ user }">
+ <user-actions :user="user" :paths="paths" :show-button-labels="true" />
+ </template>
+ </users-table>
</div>
</template>
diff --git a/app/assets/javascripts/admin/users/components/user_avatar.vue b/app/assets/javascripts/admin/users/components/user_avatar.vue
deleted file mode 100644
index dd354794cf3..00000000000
--- a/app/assets/javascripts/admin/users/components/user_avatar.vue
+++ /dev/null
@@ -1,67 +0,0 @@
-<script>
-import { GlAvatarLabeled, GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui';
-import { truncate } from '~/lib/utils/text_utility';
-import { USER_AVATAR_SIZE, LENGTH_OF_USER_NOTE_TOOLTIP } from '../constants';
-
-export default {
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- components: {
- GlAvatarLabeled,
- GlBadge,
- GlIcon,
- },
- props: {
- user: {
- type: Object,
- required: true,
- },
- adminUserPath: {
- type: String,
- required: true,
- },
- },
- computed: {
- adminUserHref() {
- return this.adminUserPath.replace('id', this.user.username);
- },
- adminUserMailto() {
- return `mailto:${this.user.email}`;
- },
- userNoteShort() {
- return truncate(this.user.note, LENGTH_OF_USER_NOTE_TOOLTIP);
- },
- },
- USER_AVATAR_SIZE,
-};
-</script>
-
-<template>
- <div
- v-if="user"
- class="js-user-link gl-display-inline-block"
- :data-user-id="user.id"
- :data-username="user.username"
- >
- <gl-avatar-labeled
- :size="$options.USER_AVATAR_SIZE"
- :src="user.avatarUrl"
- :label="user.name"
- :sub-label="user.email"
- :label-link="adminUserHref"
- :sub-label-link="adminUserMailto"
- >
- <template #meta>
- <div v-if="user.note" class="gl-text-gray-500 gl-p-1">
- <gl-icon v-gl-tooltip="userNoteShort" name="document" />
- </div>
- <div v-for="(badge, idx) in user.badges" :key="idx" class="gl-p-1">
- <gl-badge class="gl-display-flex!" size="sm" :variant="badge.variant">{{
- badge.text
- }}</gl-badge>
- </div>
- </template>
- </gl-avatar-labeled>
- </div>
-</template>
diff --git a/app/assets/javascripts/admin/users/components/users_table.vue b/app/assets/javascripts/admin/users/components/users_table.vue
deleted file mode 100644
index 65737be1e67..00000000000
--- a/app/assets/javascripts/admin/users/components/users_table.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-<script>
-import { GlSkeletonLoader, GlTable } from '@gitlab/ui';
-import { createAlert } from '~/alert';
-import { convertNodeIdsFromGraphQLIds } from '~/graphql_shared/utils';
-import { thWidthPercent } from '~/lib/utils/table_utility';
-import { s__, __ } from '~/locale';
-import UserDate from '~/vue_shared/components/user_date.vue';
-import getUsersGroupCountsQuery from '../graphql/queries/get_users_group_counts.query.graphql';
-import UserActions from './user_actions.vue';
-import UserAvatar from './user_avatar.vue';
-
-export default {
- components: {
- GlSkeletonLoader,
- GlTable,
- UserAvatar,
- UserActions,
- UserDate,
- },
- props: {
- users: {
- type: Array,
- required: true,
- },
- paths: {
- type: Object,
- required: true,
- },
- },
- data() {
- return {
- groupCounts: [],
- };
- },
- apollo: {
- groupCounts: {
- query: getUsersGroupCountsQuery,
- variables() {
- return {
- usernames: this.users.map((user) => user.username),
- };
- },
- update(data) {
- const nodes = data?.users?.nodes || [];
- const parsedIds = convertNodeIdsFromGraphQLIds(nodes);
-
- return parsedIds.reduce((acc, { id, groupCount }) => {
- acc[id] = groupCount || 0;
- return acc;
- }, {});
- },
- error(error) {
- createAlert({
- message: this.$options.i18n.groupCountFetchError,
- captureError: true,
- error,
- });
- },
- skip() {
- return !this.users.length;
- },
- },
- },
- i18n: {
- groupCountFetchError: s__(
- 'AdminUsers|Could not load user group counts. Please refresh the page to try again.',
- ),
- },
- fields: [
- {
- key: 'name',
- label: __('Name'),
- thClass: thWidthPercent(40),
- },
- {
- key: 'projectsCount',
- label: __('Projects'),
- thClass: thWidthPercent(10),
- },
- {
- key: 'groupCount',
- label: __('Groups'),
- thClass: thWidthPercent(10),
- },
- {
- key: 'createdAt',
- label: __('Created on'),
- thClass: thWidthPercent(15),
- },
- {
- key: 'lastActivityOn',
- label: __('Last activity'),
- thClass: thWidthPercent(15),
- },
- {
- key: 'settings',
- label: '',
- thClass: thWidthPercent(10),
- },
- ],
-};
-</script>
-
-<template>
- <div>
- <gl-table
- :items="users"
- :fields="$options.fields"
- :empty-text="s__('AdminUsers|No users found')"
- show-empty
- stacked="md"
- :tbody-tr-attr="{ 'data-testid': 'user-row-content' }"
- >
- <template #cell(name)="{ item: user }">
- <user-avatar :user="user" :admin-user-path="paths.adminUser" />
- </template>
-
- <template #cell(createdAt)="{ item: { createdAt } }">
- <user-date :date="createdAt" />
- </template>
-
- <template #cell(lastActivityOn)="{ item: { lastActivityOn } }">
- <user-date :date="lastActivityOn" show-never />
- </template>
-
- <template #cell(groupCount)="{ item: { id } }">
- <div :data-testid="`user-group-count-${id}`">
- <gl-skeleton-loader v-if="$apollo.loading" :width="40" :lines="1" />
- <span v-else>{{ groupCounts[id] }}</span>
- </div>
- </template>
-
- <template #cell(projectsCount)="{ item: { id, projectsCount } }">
- <div :data-testid="`user-project-count-${id}`">{{ projectsCount }}</div>
- </template>
-
- <template #cell(settings)="{ item: user }">
- <user-actions :user="user" :paths="paths" :show-button-labels="true" />
- </template>
- </gl-table>
- </div>
-</template>
diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js
index 9cd61d6b1db..73383623aa2 100644
--- a/app/assets/javascripts/admin/users/constants.js
+++ b/app/assets/javascripts/admin/users/constants.js
@@ -1,9 +1,5 @@
import { s__, __ } from '~/locale';
-export const USER_AVATAR_SIZE = 32;
-
-export const LENGTH_OF_USER_NOTE_TOOLTIP = 100;
-
export const I18N_USER_ACTIONS = {
edit: __('Edit'),
userAdministration: s__('AdminUsers|User administration'),
@@ -19,4 +15,6 @@ export const I18N_USER_ACTIONS = {
deleteWithContributions: s__('AdminUsers|Delete user and contributions'),
ban: s__('AdminUsers|Ban user'),
unban: s__('AdminUsers|Unban user'),
+ trust: s__('AdminUsers|Trust user'),
+ untrust: s__('AdminUsers|Untrust user'),
};