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`] = ` +
+

+ Comment 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', + }, + }, +};