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 3c46de7c2be228d6626df292092e57fd49ce705a..f0540ffa71e7910488e7e29eaf24d487fe07184f 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 {
/>
+
+
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 0000000000000000000000000000000000000000..80af7d7400a3a38213a208f8992ff0fdcea16e7f
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
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 8c4c1da28b84574a15bda7218980f4e8c99c24b5..2206e600543a01ec7695e1a1228f1c71479bf084 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 {
- {{ $options.i18n.activity }}
+ {{ $options.i18n.activity }}
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 0000000000000000000000000000000000000000..4d24471fa43ddeb0020e0923e0e352982710395b
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
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 0000000000000000000000000000000000000000..6da3017e11e4a6e42f25c5476bbb178d6c4b767c
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
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 0000000000000000000000000000000000000000..ab3d7f5fa6cefaee22237e059ea774bd2a6f9b9f
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note_body.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
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 84d6f25ac05463e598d9cc3dd07c3d96b12c4f88..bc1384b021d600dfb6e6f3dd9006ffb9888effb0 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 {
-
+
{{ $options.i18n.reportTypes[reportType] }}
diff --git a/app/assets/javascripts/admin/abuse_report/constants.js b/app/assets/javascripts/admin/abuse_report/constants.js
index f028408bed72870dfdbe47642c27de9eb0181fa8..c56ea678b1d475a4416a0290175e2c17a8936cea 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/graphql/abuse_report.query.graphql b/app/assets/javascripts/admin/abuse_report/graphql/abuse_report.query.graphql
index d864b44b39c1c699dd25f6e7631a43f3ba1fb5b4..640eec718f81dc34d38683ab3c3bccbbf2508581 100644
--- a/app/assets/javascripts/admin/abuse_report/graphql/abuse_report.query.graphql
+++ b/app/assets/javascripts/admin/abuse_report/graphql/abuse_report.query.graphql
@@ -1,6 +1,3 @@
-#import "./notes/abuse_report_note.fragment.graphql"
-#import "./notes/abuse_report_discussion_resolved_status.fragment.graphql"
-
query abuseReportQuery($id: AbuseReportID!) {
abuseReport(id: $id) {
id
@@ -13,17 +10,5 @@ query abuseReportQuery($id: AbuseReportID!) {
textColor
}
}
- discussions {
- nodes {
- id
- replyId
- ...AbuseReportDiscussionResolvedStatus
- notes {
- nodes {
- ...AbuseReportNote
- }
- }
- }
- }
}
}
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_discussion_resolved_status.fragment.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_discussion_resolved_status.fragment.graphql
deleted file mode 100644
index 5ca56c2ded2308a038ed7245dc3b03f7a332674a..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_discussion_resolved_status.fragment.graphql
+++ /dev/null
@@ -1,11 +0,0 @@
-fragment AbuseReportDiscussionResolvedStatus on Discussion {
- id
- resolvable
- resolved
- resolvedAt
- resolvedBy {
- id
- name
- webUrl
- }
-}
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
index 43bd2b3a6e62157b79fee7ed58ab1e36ca0b47bb..84b57b4ed79ede1396065ddac6df5577be5023eb 100644
--- 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
@@ -3,13 +3,19 @@
fragment AbuseReportNote on Note {
id
- author {
- ...Author
- }
body
bodyHtml
createdAt
+ lastEditedAt
+ url
resolved
+ author {
+ ...Author
+ }
+ lastEditedBy {
+ ...Author
+ webPath
+ }
userPermissions {
...AbuseReportNotePermissions
}
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 0000000000000000000000000000000000000000..3a13ac1f37a1f13189a1f2fdade3d4a32fe277bd
--- /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/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb
index b48d6f4f7c2fe0762ba92369630e8482ddadd59a..d5c505ba1dd8c00298d5ed3ba624b562aa95f869 100644
--- a/app/controllers/admin/abuse_reports_controller.rb
+++ b/app/controllers/admin/abuse_reports_controller.rb
@@ -7,6 +7,7 @@ class Admin::AbuseReportsController < Admin::ApplicationController
before_action :find_abuse_report, only: [:show, :moderate_user, :update, :destroy]
before_action only: :show do
push_frontend_feature_flag(:abuse_report_labels)
+ push_frontend_feature_flag(:abuse_report_notes)
end
def index
diff --git a/app/views/admin/abuse_reports/show.html.haml b/app/views/admin/abuse_reports/show.html.haml
index bd7a1054b5d4f18b96b53ba060f7d7acb64037f0..ff9ac6a052cf5efe4e5a5c7f85cdfe0481166f37 100644
--- a/app/views/admin/abuse_reports/show.html.haml
+++ b/app/views/admin/abuse_reports/show.html.haml
@@ -1,5 +1,6 @@
- add_to_breadcrumbs _('Abuse Reports'), admin_abuse_reports_path
- breadcrumb_title @abuse_report.user&.name
+- @content_class = "limit-container-width" unless fluid_layout
- page_title @abuse_report.user&.name, _('Abuse Reports')
#js-abuse-reports-detail-view{ data: abuse_report_data(@abuse_report) }
diff --git a/config/feature_flags/development/abuse_report_notes.yml b/config/feature_flags/development/abuse_report_notes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9378b1a1d899622f1dd74c47f074e38da3334d81
--- /dev/null
+++ b/config/feature_flags/development/abuse_report_notes.yml
@@ -0,0 +1,8 @@
+---
+name: abuse_report_notes
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134730
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429027
+milestone: '16.6'
+type: development
+group: group::anti-abuse
+default_enabled: false
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e4ed3e83e59f52af58708775bce73cd0535b81d5..ec96b93785be0d1ad9995015fee66a2b0639bc40 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5121,6 +5121,9 @@ msgstr ""
msgid "An error occurred while fetching codequality mr diff reports."
msgstr ""
+msgid "An error occurred while fetching comments, please try again."
+msgstr ""
+
msgid "An error occurred while fetching commit data."
msgstr ""
diff --git a/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js b/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js
index 4340699a7ed3247c4eead849f43dc5b2d78e4ff2..73e3f1eb49a06428089e4bd7ac02a5d685fe3f03 100644
--- a/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js
+++ b/spec/frontend/admin/abuse_report/components/abuse_report_app_spec.js
@@ -7,12 +7,14 @@ import ReportDetails from '~/admin/abuse_report/components/report_details.vue';
import ReportedContent from '~/admin/abuse_report/components/reported_content.vue';
import ActivityEventsList from '~/admin/abuse_report/components/activity_events_list.vue';
import ActivityHistoryItem from '~/admin/abuse_report/components/activity_history_item.vue';
+import AbuseReportNotes from '~/admin/abuse_report/components/abuse_report_notes.vue';
+
import { SUCCESS_ALERT } from '~/admin/abuse_report/constants';
import { mockAbuseReport } from '../mock_data';
describe('AbuseReportApp', () => {
let wrapper;
-
+ const mockAbuseReportId = mockAbuseReport.report.globalId;
const { similarOpenReports } = mockAbuseReport.user;
const findAlert = () => wrapper.findComponent(GlAlert);
@@ -27,6 +29,7 @@ describe('AbuseReportApp', () => {
const findActivityList = () => wrapper.findComponent(ActivityEventsList);
const findActivityItem = () => wrapper.findByTestId('activity');
+
const findActivityForSimilarReports = () =>
wrapper.findAllByTestId('activity-similar-open-reports');
const firstActivityForSimilarReports = () =>
@@ -34,6 +37,8 @@ describe('AbuseReportApp', () => {
const findReportDetails = () => wrapper.findComponent(ReportDetails);
+ const findAbuseReportNotes = () => wrapper.findComponent(AbuseReportNotes);
+
const createComponent = (props = {}, provide = {}) => {
wrapper = shallowMountExtended(AbuseReportApp, {
propsData: {
@@ -135,7 +140,7 @@ describe('AbuseReportApp', () => {
it('renders ReportDetails', () => {
createComponent({}, { glFeatures: { abuseReportLabels: true } });
- expect(findReportDetails().props('reportId')).toBe(mockAbuseReport.report.globalId);
+ expect(findReportDetails().props('reportId')).toBe(mockAbuseReportId);
});
});
@@ -162,4 +167,25 @@ describe('AbuseReportApp', () => {
expect(firstActivityForSimilarReports().props('report')).toBe(similarOpenReports[0]);
});
});
+
+ describe('Notes', () => {
+ describe('when abuseReportNotes feature flag is enabled', () => {
+ it('renders abuse report notes', () => {
+ createComponent({}, { glFeatures: { abuseReportNotes: true } });
+
+ expect(findAbuseReportNotes().exists()).toBe(true);
+ expect(findAbuseReportNotes().props()).toMatchObject({
+ abuseReportId: mockAbuseReportId,
+ });
+ });
+ });
+
+ describe('when abuseReportNotes feature flag is disabled', () => {
+ it('does not render ReportDetails', () => {
+ createComponent({}, { glFeatures: { abuseReportNotes: false } });
+
+ expect(findAbuseReportNotes().exists()).toBe(false);
+ });
+ });
+ });
});
diff --git a/spec/frontend/admin/abuse_report/components/abuse_report_notes_spec.js b/spec/frontend/admin/abuse_report/components/abuse_report_notes_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..166c735ffbd2b7844ae88fc601ddf8e67a42b686
--- /dev/null
+++ b/spec/frontend/admin/abuse_report/components/abuse_report_notes_spec.js
@@ -0,0 +1,98 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
+import SkeletonLoadingContainer from '~/vue_shared/components/notes/skeleton_note.vue';
+import abuseReportNotesQuery from '~/admin/abuse_report/graphql/notes/abuse_report_notes.query.graphql';
+import AbuseReportNotes from '~/admin/abuse_report/components/abuse_report_notes.vue';
+import AbuseReportDiscussion from '~/admin/abuse_report/components/notes/abuse_report_discussion.vue';
+
+import { mockAbuseReport, mockNotesByIdResponse } from '../mock_data';
+
+jest.mock('~/alert');
+
+describe('Abuse Report Notes', () => {
+ let wrapper;
+
+ Vue.use(VueApollo);
+
+ const mockAbuseReportId = mockAbuseReport.report.globalId;
+
+ const notesQueryHandler = jest.fn().mockResolvedValue(mockNotesByIdResponse);
+
+ const findSkeletonLoaders = () => wrapper.findAllComponents(SkeletonLoadingContainer);
+ const findAbuseReportDiscussions = () => wrapper.findAllComponents(AbuseReportDiscussion);
+
+ const createComponent = ({
+ queryHandler = notesQueryHandler,
+ abuseReportId = mockAbuseReportId,
+ } = {}) => {
+ wrapper = shallowMount(AbuseReportNotes, {
+ apolloProvider: createMockApollo([[abuseReportNotesQuery, queryHandler]]),
+ propsData: {
+ abuseReportId,
+ },
+ });
+ };
+
+ describe('when notes are loading', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should show the skeleton loaders', () => {
+ expect(findSkeletonLoaders()).toHaveLength(5);
+ });
+ });
+
+ describe('when notes have been loaded', () => {
+ beforeEach(() => {
+ createComponent();
+ return waitForPromises();
+ });
+
+ it('should not render skeleton loader', () => {
+ expect(findSkeletonLoaders()).toHaveLength(0);
+ });
+
+ it('should call the abuse report notes query', () => {
+ expect(notesQueryHandler).toHaveBeenCalledWith({
+ id: mockAbuseReportId,
+ });
+ });
+
+ it('should show notes to the length of the response', () => {
+ expect(findAbuseReportDiscussions()).toHaveLength(2);
+
+ const discussions = mockNotesByIdResponse.data.abuseReport.discussions.nodes;
+
+ expect(findAbuseReportDiscussions().at(0).props()).toMatchObject({
+ abuseReportId: mockAbuseReportId,
+ discussion: discussions[0].notes.nodes,
+ });
+
+ expect(findAbuseReportDiscussions().at(1).props()).toMatchObject({
+ abuseReportId: mockAbuseReportId,
+ discussion: discussions[1].notes.nodes,
+ });
+ });
+ });
+
+ describe('When there is an error fetching the notes', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: jest.fn().mockRejectedValue(new Error()),
+ });
+
+ return waitForPromises();
+ });
+
+ it('should show an error when query fails', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'An error occurred while fetching comments, please try again.',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/abuse_report/components/notes/__snapshots__/abuse_report_note_body_spec.js.snap b/spec/frontend/admin/abuse_report/components/notes/__snapshots__/abuse_report_note_body_spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..5651a2a3eab7d44747f2c1234b6a9acbfd9ac74c
--- /dev/null
+++ b/spec/frontend/admin/abuse_report/components/notes/__snapshots__/abuse_report_note_body_spec.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Abuse Report Note Body should show the note body 1`] = `
+
+`;
diff --git a/spec/frontend/admin/abuse_report/components/notes/abuse_report_discussion_spec.js b/spec/frontend/admin/abuse_report/components/notes/abuse_report_discussion_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..86f0939a9385d5a3ddabfbca525f437381852ca9
--- /dev/null
+++ b/spec/frontend/admin/abuse_report/components/notes/abuse_report_discussion_spec.js
@@ -0,0 +1,79 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
+import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import AbuseReportDiscussion from '~/admin/abuse_report/components/notes/abuse_report_discussion.vue';
+import AbuseReportNote from '~/admin/abuse_report/components/notes/abuse_report_note.vue';
+
+import {
+ mockAbuseReport,
+ mockDiscussionWithNoReplies,
+ mockDiscussionWithReplies,
+} from '../../mock_data';
+
+describe('Abuse Report Discussion', () => {
+ let wrapper;
+ const mockAbuseReportId = mockAbuseReport.report.globalId;
+
+ const findAbuseReportNote = () => wrapper.findComponent(AbuseReportNote);
+ const findAbuseReportNotes = () => wrapper.findAllComponents(AbuseReportNote);
+ const findTimelineEntryItem = () => wrapper.findComponent(TimelineEntryItem);
+ const findToggleRepliesWidget = () => wrapper.findComponent(ToggleRepliesWidget);
+
+ const createComponent = ({
+ discussion = mockDiscussionWithNoReplies,
+ abuseReportId = mockAbuseReportId,
+ } = {}) => {
+ wrapper = shallowMount(AbuseReportDiscussion, {
+ propsData: {
+ discussion,
+ abuseReportId,
+ },
+ });
+ };
+
+ describe('Default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should show the abuse report note', () => {
+ expect(findAbuseReportNote().exists()).toBe(true);
+
+ expect(findAbuseReportNote().props()).toMatchObject({
+ abuseReportId: mockAbuseReportId,
+ note: mockDiscussionWithNoReplies[0],
+ });
+ });
+
+ it('should not show timeline entry item component', () => {
+ expect(findTimelineEntryItem().exists()).toBe(false);
+ });
+
+ it('should not show the the toggle replies widget wrapper when no replies', () => {
+ expect(findToggleRepliesWidget().exists()).toBe(false);
+ });
+ });
+
+ describe('When the main comments has replies', () => {
+ beforeEach(() => {
+ createComponent({
+ discussion: mockDiscussionWithReplies,
+ });
+ });
+
+ it('should show the toggle replies widget', () => {
+ expect(findToggleRepliesWidget().exists()).toBe(true);
+ });
+
+ it('the number of replies should be equal to the response length', () => {
+ expect(findAbuseReportNotes()).toHaveLength(3);
+ });
+
+ it('should collapse when we click on toggle replies widget', async () => {
+ findToggleRepliesWidget().vm.$emit('toggle');
+ await nextTick();
+ expect(findAbuseReportNotes()).toHaveLength(1);
+ });
+ });
+});
diff --git a/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_body_spec.js b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_body_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..25f675b4562c152b03830e80b8d81d212923bedf
--- /dev/null
+++ b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_body_spec.js
@@ -0,0 +1,27 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import AbuseReportNoteBody from '~/admin/abuse_report/components/notes/abuse_report_note_body.vue';
+import { mockDiscussionWithNoReplies } from '../../mock_data';
+
+describe('Abuse Report Note Body', () => {
+ let wrapper;
+ const mockNote = mockDiscussionWithNoReplies[0];
+
+ const findNoteBody = () => wrapper.findByTestId('abuse-report-note-body');
+
+ const createComponent = ({ note = mockNote } = {}) => {
+ wrapper = shallowMountExtended(AbuseReportNoteBody, {
+ propsData: {
+ note,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should show the note body', () => {
+ expect(findNoteBody().exists()).toBe(true);
+ expect(findNoteBody().html()).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b6908853e46841ece2d548f78253415b116bb9dc
--- /dev/null
+++ b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
@@ -0,0 +1,80 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlAvatarLink, GlAvatar } from '@gitlab/ui';
+import AbuseReportNote from '~/admin/abuse_report/components/notes/abuse_report_note.vue';
+import NoteHeader from '~/notes/components/note_header.vue';
+import NoteBody from '~/admin/abuse_report/components/notes/abuse_report_note_body.vue';
+
+import { mockAbuseReport, mockDiscussionWithNoReplies } from '../../mock_data';
+
+describe('Abuse Report Note', () => {
+ let wrapper;
+ const mockAbuseReportId = mockAbuseReport.report.globalId;
+ const mockNote = mockDiscussionWithNoReplies[0];
+
+ const findAvatar = () => wrapper.findComponent(GlAvatar);
+ const findAvatarLink = () => wrapper.findComponent(GlAvatarLink);
+
+ const findNoteHeader = () => wrapper.findComponent(NoteHeader);
+ const findNoteBody = () => wrapper.findComponent(NoteBody);
+
+ const createComponent = ({ note = mockNote, abuseReportId = mockAbuseReportId } = {}) => {
+ wrapper = shallowMount(AbuseReportNote, {
+ propsData: {
+ note,
+ abuseReportId,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('Author', () => {
+ const { author } = mockNote;
+
+ it('should show avatar', () => {
+ const avatar = findAvatar();
+
+ expect(avatar.exists()).toBe(true);
+ expect(avatar.props()).toMatchObject({
+ src: author.avatarUrl,
+ entityName: author.username,
+ alt: author.name,
+ });
+ });
+
+ it('should show avatar link with popover support', () => {
+ const avatarLink = findAvatarLink();
+
+ expect(avatarLink.exists()).toBe(true);
+ expect(avatarLink.classes()).toContain('js-user-link');
+ expect(avatarLink.attributes()).toMatchObject({
+ href: author.webUrl,
+ 'data-user-id': '1',
+ 'data-username': `${author.username}`,
+ });
+ });
+ });
+
+ describe('Header', () => {
+ it('should show note header', () => {
+ expect(findNoteHeader().exists()).toBe(true);
+ expect(findNoteHeader().props()).toMatchObject({
+ author: mockNote.author,
+ createdAt: mockNote.createdAt,
+ noteId: mockNote.id,
+ noteUrl: mockNote.url,
+ });
+ });
+ });
+
+ describe('Body', () => {
+ it('should show note body', () => {
+ expect(findNoteBody().exists()).toBe(true);
+ expect(findNoteBody().props()).toMatchObject({
+ note: mockNote,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/abuse_report/mock_data.js b/spec/frontend/admin/abuse_report/mock_data.js
index c7faddf1d13c9953af2844d4f5df21c781bb3224..44c8cbdad7f1cadbfde57391cc52f68b2e8a8aad 100644
--- a/spec/frontend/admin/abuse_report/mock_data.js
+++ b/spec/frontend/admin/abuse_report/mock_data.js
@@ -132,3 +132,211 @@ export const mockCreateLabelResponse = {
},
},
};
+
+export const mockDiscussionWithNoReplies = [
+ {
+ id: 'gid://gitlab/Note/1',
+ body: 'Comment 1',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:9" dir="auto"\u003eComment 1\u003c/p\u003e',
+ createdAt: '2023-10-19T06:11:13Z',
+ lastEditedAt: '2023-10-20T02:46:50Z',
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_1',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ userPermissions: {
+ adminNote: true,
+ __typename: 'NotePermissions',
+ },
+ discussion: {
+ id: 'gid://gitlab/Discussion/055af96ab917175219aec8739c911277b18ea41d',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Note/1',
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ __typename: 'Note',
+ },
+];
+export const mockDiscussionWithReplies = [
+ {
+ id: 'gid://gitlab/DiscussionNote/2',
+ body: 'Comment 2',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:9" dir="auto"\u003eComment 2\u003c/p\u003e',
+ createdAt: '2023-10-20T07:47:21Z',
+ lastEditedAt: '2023-10-20T07:47:42Z',
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_2',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ userPermissions: {
+ adminNote: true,
+ __typename: 'NotePermissions',
+ },
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/DiscussionNote/2',
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/3',
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/4',
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/3',
+ body: 'Reply comment 1',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:15" dir="auto"\u003eReply comment 1\u003c/p\u003e',
+ createdAt: '2023-10-20T07:47:42Z',
+ lastEditedAt: '2023-10-20T07:47:42Z',
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_3',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ userPermissions: {
+ adminNote: true,
+ __typename: 'NotePermissions',
+ },
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/DiscussionNote/2',
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/3',
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/4',
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/4',
+ body: 'Reply comment 2',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:15" dir="auto"\u003eReply comment 2\u003c/p\u003e',
+ createdAt: '2023-10-20T08:26:51Z',
+ lastEditedAt: '2023-10-20T08:26:51Z',
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_4',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ userPermissions: {
+ adminNote: true,
+ __typename: 'NotePermissions',
+ },
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/DiscussionNote/2',
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/3',
+ __typename: 'Note',
+ },
+ {
+ id: 'gid://gitlab/DiscussionNote/4',
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ __typename: 'Note',
+ },
+];
+
+export const mockNotesByIdResponse = {
+ data: {
+ abuseReport: {
+ id: 'gid://gitlab/AbuseReport/1',
+ discussions: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Discussion/055af96ab917175219aec8739c911277b18ea41d',
+ replyId:
+ 'gid://gitlab/IndividualNoteDiscussion/055af96ab917175219aec8739c911277b18ea41d',
+ notes: {
+ nodes: mockDiscussionWithNoReplies,
+ __typename: 'NoteConnection',
+ },
+ },
+ {
+ id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ replyId: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ notes: {
+ nodes: mockDiscussionWithReplies,
+ __typename: 'NoteConnection',
+ },
+ },
+ ],
+ __typename: 'DiscussionConnection',
+ },
+ __typename: 'AbuseReport',
+ },
+ },
+};